閉じる

【Python】timedeltaで日付計算をする方法〜実務レシピ大全

Pythonで日付計算を行うとき、最初にぶつかるのが「datetimeとtimedeltaの違い」と、「何日後・何時間後をどう計算するか」という問題です。

本記事では、Python標準ライブラリのdatetime.timedeltaを使って、実務で頻出の「締切」「有効期限」「レポート期間」「シフト時間」などを正確に扱う方法を、図解とコード例付きで詳しく解説します。

Pythonのtimedeltaとは

datetimeとtimedeltaの基本関係

Pythonで日付や時刻を扱うときは、主にdatetimeモジュールを使います。

この中で「いつ」を表すのがdatetime.datetimedatetime.dateで、「どれくらいの長さ」を表すのがdatetime.timedeltaです。

datetimeは「点」timedeltaは「長さ(期間)」だとイメージすると理解しやすくなります。

代表的な関係は次のようになります。

  • datetime + timedelta = datetime (何日後、何時間後を求める)
  • datetime - timedelta = datetime (何日前、何時間前を求める)
  • datetime - datetime = timedelta (2つの日時の差を求める)

具体的な型の関係を表にすると次のようになります。

結果の型用途のイメージ
dt1 - dt2timedelta2つの日時の差分(日数・時間)を知る
dt + tddatetimen日後・n時間後を計算
dt - tddatetimen日前・n時間前を計算

ここで最も重要なのは、timedeltaは日付そのものではなく「期間」を表すオブジェクトだという点です。

datetimeとtimedeltaを軽く触ってみる

Python
from datetime import datetime, timedelta

# 現在時刻を取得
now = datetime.now()

# 3日を表すtimedelta
three_days = timedelta(days=3)

# 3日後
three_days_later = now + three_days

# 3日前
three_days_before = now - three_days

print("現在:", now)
print("3日後:", three_days_later)
print("3日前:", three_days_before)
print("3日という期間の実体:", three_days)

上記コードを実行すると、同じdatetimeからtimedeltaを使って「前」「後」の日時が得られることが確認できます。

timedeltaで扱える時間の単位

timedeltaは、次のような時間の単位を引数として指定できます。

  • days
  • seconds
  • microseconds
  • milliseconds
  • minutes
  • hours
  • weeks

内部的には日・秒・マイクロ秒に正規化されますが、指定時には上記の便利な単位を使えます。

Python
from datetime import timedelta

# 各種単位の指定例
td = timedelta(
    weeks=1,       # 1週間
    days=2,        # 2日
    hours=3,       # 3時間
    minutes=30,    # 30分
    seconds=15,    # 15秒
    milliseconds=500,  # 0.5秒
    microseconds=250   # 0.00025秒
)

print("timedeltaの中身:", td)
print("日数(td.days):", td.days)
print("秒(td.seconds):", td.seconds)
print("マイクロ秒(td.microseconds):", td.microseconds)
print("合計秒数(td.total_seconds()):", td.total_seconds())
実行結果
timedeltaの中身: 9 days, 3:30:15.500250
日数(td.days): 9
秒(td.seconds): 12615
マイクロ秒(td.microseconds): 500250
合計秒数(td.total_seconds()): 797415.50025

日・秒・マイクロ秒の3要素から、合計秒数はtotal_seconds()で取得できます

後ほど差分計算で詳しく扱います。

timedeltaで日付・時間を加算する

日付に日数を足す(add)基本例

最も基本的な使い方は、特定の日付に日数を足して「N日後」を求める方法です。

Python
from datetime import date, timedelta

start = date(2025, 4, 1)          # 2025年4月1日
delta = timedelta(days=3)         # 3日間

result = start + delta            # 3日後

print("開始日:", start)
print("3日後:", result)
実行結果
開始日: 2025-04-01
3日後: 2025-04-04

date型でもdatetime型でも、足し算・引き算の考え方は同じです。

Python
from datetime import datetime, timedelta

start_dt = datetime(2025, 4, 1, 9, 0, 0)   # 2025-04-01 09:00:00
one_day = timedelta(days=1)

print("開始日時:", start_dt)
print("1日後:", start_dt + one_day)
print("1日前:", start_dt - one_day)

営業日や締め日計算にtimedeltaを使う

実務では、単純な「7日後」ではなく「5営業日後」「毎月末締め」のような計算が頻出します。

ここでは、土日を除いて営業日を数える簡易的な例を示します。

Python
from datetime import date, timedelta

def add_business_days(start_date: date, days: int) -> date:
    """土日を除いて指定営業日数を加算する簡易関数"""
    current = start_date
    added_days = 0

    while added_days < days:
        current += timedelta(days=1)
        # 月曜日=0, ..., 日曜日=6
        if current.weekday() < 5:  # 0〜4が平日
            added_days += 1
    return current

start = date(2025, 4, 1)  # 火曜日と仮定
target = add_business_days(start, 5)

print("開始日:", start)
print("5営業日後:", target)
実行結果
開始日: 2025-04-01
5営業日後: 2025-04-08

ここでは土日だけを除外していますが、祝日を考慮する場合は別途「祝日リスト」を用意し、それに含まれる日もスキップするように拡張します。

Python
from datetime import date, timedelta

JAPAN_HOLIDAYS_2025 = {
    date(2025, 1, 1),  # 元日 (例)
    date(2025, 1, 13), # 成人の日 (例)
    # ... 必要に応じて追加
}

def add_business_days_with_holidays(start_date: date, days: int) -> date:
    current = start_date
    added_days = 0

    while added_days < days:
        current += timedelta(days=1)
        is_weekend = current.weekday() >= 5
        is_holiday = current in JAPAN_HOLIDAYS_2025

        if not is_weekend and not is_holiday:
            added_days += 1

    return current

締め日計算では、例えば「末日から3営業日前」を求めることもよくあります。

Python
from datetime import date, timedelta
import calendar

def last_day_of_month(year: int, month: int) -> date:
    """月末日を返す"""
    last = calendar.monthrange(year, month)[1]
    return date(year, month, last)

def last_3_business_days_before_month_end(year: int, month: int) -> date:
    month_end = last_day_of_month(year, month)
    # ここでは土日だけ除外する簡易版
    current = month_end
    counted = 0

    while counted < 3:
        if current.weekday() < 5:  # 平日
            counted += 1
            if counted == 3:
                break
        current -= timedelta(days=1)

    return current

print("2025年4月の月末日:", last_day_of_month(2025, 4))
print("月末から3営業日前:", last_3_business_days_before_month_end(2025, 4))
実行結果
2025年4月の月末日: 2025-04-30
月末から3営業日前: 2025-04-25

このようにtimedeltaを1日ずつ加算・減算しながら条件でスキップしていくことで、柔軟な締め日計算が可能になります。

時間単位(時・分・秒)の加算でシフト時間を計算

日付だけでなく、時刻ベースのシフトや稼働時間計算にもtimedeltaは有効です。

Python
from datetime import datetime, timedelta

# 2025-04-01 9:00 出勤
clock_in = datetime(2025, 4, 1, 9, 0, 0)

# 実働8時間 + 休憩1時間 のシフト
work_hours = timedelta(hours=8)
break_time = timedelta(hours=1)

# 退勤時間 = 出勤 + 実働 + 休憩
clock_out = clock_in + work_hours + break_time

print("出勤:", clock_in)
print("退勤(8h勤務+1h休憩):", clock_out)
実行結果
出勤: 2025-04-01 09:00:00
退勤(8h勤務+1h休憩): 2025-04-01 18:00:00

分単位でシフトパターンを扱うこともよくあります。

Python
from datetime import datetime, timedelta

clock_in = datetime(2025, 4, 1, 22, 30)   # 22:30出勤
shift_length = timedelta(hours=7, minutes=45)  # 7時間45分

clock_out = clock_in + shift_length

print("出勤:", clock_in)
print("退勤:", clock_out)
実行結果
出勤: 2025-04-01 22:30:00
退勤: 2025-04-02 06:15:00

日をまたぐ夜勤シフトでも、単純にtimedeltaを足すだけで翌日に繰り上がるため、複雑なロジックを書かずに済みます。

timedeltaで差分を求める

2つのdatetimeからtimedeltaを取得する方法

2つの日時の差分を求めるには、単純に引き算を行うだけです。

Python
from datetime import datetime

start = datetime(2025, 4, 1, 9, 0, 0)
end   = datetime(2025, 4, 3, 18, 30, 0)

diff = end - start

print("開始:", start)
print("終了:", end)
print("差分:", diff)
print("差分の型:", type(diff))
実行結果
開始: 2025-04-01 09:00:00
終了: 2025-04-03 18:30:00
差分: 2 days, 9:30:00
差分の型: <class 'datetime.timedelta'>

datetime同士の差は必ずtimedeltaになります

このtimedeltaから、必要な単位(日・時間・秒など)を取り出していきます。

日数差・時間差・秒数差を取り出す

timedeltaには、主に次のようなプロパティやメソッドがあります。

  • days … 日数部分(整数)
  • seconds … 1日未満の秒数部分(0〜86399)
  • microseconds … 残りのマイクロ秒部分
  • total_seconds() … 全体を秒数に換算した浮動小数点
Python
from datetime import datetime

start = datetime(2025, 4, 1, 9, 0, 0)
end   = datetime(2025, 4, 3, 18, 30, 0)

diff = end - start

print("差分:", diff)
print("日数部 diff.days:", diff.days)
print("1日未満の秒 diff.seconds:", diff.seconds)
print("マイクロ秒 diff.microseconds:", diff.microseconds)
print("合計秒 diff.total_seconds():", diff.total_seconds())

# 合計時間(時間単位)に変換
hours = diff.total_seconds() / 3600
print("合計時間(時間):", hours)
実行結果
差分: 2 days, 9:30:00
日数部 diff.days: 2
1日未満の秒 diff.seconds: 34200
マイクロ秒 diff.microseconds: 0
合計秒 diff.total_seconds(): 198600.0
合計時間(時間): 55.166666666666664

「何日と何時間か」を分けて表示したい場合は、合計秒から手計算で分解します。

Python
total_seconds = diff.total_seconds()

days = total_seconds // 86400       # 1日=86400秒
remain = total_seconds % 86400

hours = remain // 3600              # 1時間=3600秒
remain %= 3600

minutes = remain // 60              # 1分=60秒
seconds = remain % 60

print(f"{int(days)}日 {int(hours)}時間 {int(minutes)}分 {int(seconds)}秒")
実行結果
2日 9時間 30分 0秒

日数・時間・分・秒のレポート形式がよく求められるため、このような分解ロジックを関数化しておくと便利です。

経過日数・経過時間のレポートを作成する

実務では、あるタスクやプロジェクトの経過時間を定期的にレポートすることがあります。

ここでは、開始日時と現在日時から、見やすいテキストレポートを作る例を紹介します。

Python
from datetime import datetime

def format_timedelta_human_readable(delta):
    """timedeltaを「X日Y時間Z分」の文字列に整形する"""
    total_seconds = int(delta.total_seconds())
    sign = "-" if total_seconds < 0 else ""
    total_seconds = abs(total_seconds)

    days, rem = divmod(total_seconds, 86400)
    hours, rem = divmod(rem, 3600)
    minutes, seconds = divmod(rem, 60)

    parts = []
    if days:
        parts.append(f"{days}日")
    if hours:
        parts.append(f"{hours}時間")
    if minutes:
        parts.append(f"{minutes}分")
    if seconds or not parts:
        parts.append(f"{seconds}秒")

    return sign + "".join(parts)

project_start = datetime(2025, 1, 1, 9, 0, 0)
now = datetime(2025, 4, 1, 18, 0, 0)

elapsed = now - project_start

print("プロジェクト開始:", project_start)
print("現在:", now)
print("経過:", format_timedelta_human_readable(elapsed))
実行結果
プロジェクト開始: 2025-01-01 09:00:00
現在: 2025-04-01 18:00:00
経過: 90日9時間

人間が読むレポートでは、単純な秒数ではなく読みやすい単位に分割することが重要です。

この関数は、遅延や残り時間の表示にもそのまま応用できます。

実務で使えるtimedeltaレシピ集

月次・週次レポート期間をtimedeltaで計算

BIレポートやバッチ処理では、「直近1週間」「先月1ヶ月分」などの集計期間をプログラムで計算する必要があります。

ここでもtimedeltaが役立ちます。

直近7日間の期間を求める

Python
from datetime import date, timedelta

today = date(2025, 4, 10)

# 直近7日間(今日を含む)の開始日と終了日
days = 7
end_date = today
start_date = today - timedelta(days=days - 1)

print("直近7日間の開始日:", start_date)
print("直近7日間の終了日:", end_date)
実行結果
直近7日間の開始日: 2025-04-04
直近7日間の終了日: 2025-04-10

週次レポート(月曜始まり〜日曜終わり)

Python
from datetime import date, timedelta

def get_week_range(target: date):
    """指定日を含む週(月曜〜日曜)の開始日・終了日を返す"""
    # weekday(): 月=0, ... 日=6
    monday = target - timedelta(days=target.weekday())
    sunday = monday + timedelta(days=6)
    return monday, sunday

target = date(2025, 4, 10)  # 木曜日と仮定
start_week, end_week = get_week_range(target)

print("対象日:", target)
print("週次開始(月):", start_week)
print("週次終了(日):", end_week)
実行結果
対象日: 2025-04-10
週次開始(月): 2025-04-07
週次終了(日): 2025-04-13

月次レポート(1日〜末日)

Python
from datetime import date
import calendar

def get_month_range(year: int, month: int):
    """指定年・月の月初日と月末日を返す"""
    first = date(year, month, 1)
    last_day = calendar.monthrange(year, month)[1]
    last = date(year, month, last_day)
    return first, last

start_month, end_month = get_month_range(2025, 4)

print("月初日:", start_month)
print("月末日:", end_month)
実行結果
月初日: 2025-04-01
月末日: 2025-04-30

月の長さは28〜31日で変動するため、calendar.monthrangeと組み合わせて計算するのが安全です。

有効期限・締切日時をtimedeltaで管理

クーポンやトークン、申請締切など、有効期限の管理はほぼすべてのシステムで登場します

timedeltaを使えば、現在時刻との差分でシンプルに判定できます。

Python
from datetime import datetime, timedelta

def is_expired(issued_at: datetime, lifetime_hours: int, now: datetime) -> bool:
    """発行時刻から指定時間が経過しているかを判定"""
    expires_at = issued_at + timedelta(hours=lifetime_hours)
    return now >= expires_at

issued_at = datetime(2025, 4, 1, 10, 0, 0)
now = datetime(2025, 4, 1, 18, 0, 0)

expired = is_expired(issued_at, lifetime_hours=6, now=now)

print("発行時刻:", issued_at)
print("現在:", now)
print("有効期限切れか:", expired)
実行結果
発行時刻: 2025-04-01 10:00:00
現在: 2025-04-01 18:00:00
有効期限切れか: True

より詳細な残り時間を表示することもあります。

Python
def get_remaining_time(issued_at: datetime, lifetime_hours: int, now: datetime) -> timedelta:
    expires_at = issued_at + timedelta(hours=lifetime_hours)
    return expires_at - now

issued_at = datetime(2025, 4, 1, 10, 0, 0)
now = datetime(2025, 4, 1, 13, 30, 0)

remaining = get_remaining_time(issued_at, lifetime_hours=6, now=now)

print("残り時間(timedelta):", remaining)
print("残り時間(秒):", remaining.total_seconds())
print("残り時間(人間向け):", format_timedelta_human_readable(remaining))
実行結果
残り時間(timedelta): 2:30:00
残り時間(秒): 9000.0
残り時間(人間向け): 2時間30分

「締切日時 = 基準日時 + timedelta」、「締切判定 = 現在日時と締切日時の比較」という形で統一しておくと、仕様変更にも対応しやすくなります。

ログやセンサー値の間隔チェックにtimedeltaを活用

監視やIoTの現場では、ログやセンサー値が「想定どおりの頻度で届いているか」をチェックすることがよくあります。

このときも、timedeltaを使って直前のデータとの時間差を見ればよいです。

Python
from datetime import datetime, timedelta

# ログのタイムスタンプ例
timestamps = [
    datetime(2025, 4, 1, 10, 0, 0),
    datetime(2025, 4, 1, 10, 1, 0),
    datetime(2025, 4, 1, 10, 2, 5),  # ここだけ少し遅い
    datetime(2025, 4, 1, 10, 3, 0),
    datetime(2025, 4, 1, 10, 6, 30), # ここは大きく遅延
]

EXPECTED_INTERVAL = timedelta(minutes=1)
ALERT_THRESHOLD = timedelta(minutes=2)

prev = None
for ts in timestamps:
    if prev is not None:
        interval = ts - prev
        print(f"{prev} -> {ts} 間隔: {interval}")
        if interval > ALERT_THRESHOLD:
            print("  !! アラート: 間隔が長すぎます")
    prev = ts
実行結果
2025-04-01 10:00:00 -> 2025-04-01 10:01:00 間隔: 0:01:00
2025-04-01 10:01:00 -> 2025-04-01 10:02:05 間隔: 0:01:05
2025-04-01 10:02:05 -> 2025-04-01 10:03:00 間隔: 0:00:55
2025-04-01 10:03:00 -> 2025-04-01 10:06:30 間隔: 0:03:30
  !! アラート: 間隔が長すぎます

ここでは想定間隔(1分)よりも大きく離れた値(ALERT_THRESHOLD超え)を異常として検出しています。

さらに、平均間隔や最大間隔をレポートにまとめることもできます。

Python
def analyze_intervals(timestamps):
    intervals = []
    prev = None
    for ts in timestamps:
        if prev is not None:
            intervals.append(ts - prev)
        prev = ts

    if not intervals:
        return None

    total = sum((i for i in intervals), timedelta())
    avg = total / len(intervals)
    max_interval = max(intervals)

    return {
        "count": len(intervals),
        "total": total,
        "average": avg,
        "max": max_interval,
    }

result = analyze_intervals(timestamps)

print("測定数(間隔の数):", result["count"])
print("合計間隔:", result["total"])
print("平均間隔:", result["average"])
print("最大間隔:", result["max"])
実行結果
測定数(間隔の数): 4
合計間隔: 0:06:35
平均間隔: 0:01:38.750000
最大間隔: 0:03:30

timedelta同士は足し算・割り算もできるため、このような統計的な処理にも自然に使うことができます。

まとめ

timedeltaは「期間」を表すクラスであり、datetimeと組み合わせることで日付・時刻の加算や差分計算をシンプルに記述できます

日数・時間・分・秒レベルの計算だけでなく、営業日計算や締め日、有効期限、ログ間隔チェック、週次・月次レポート期間の算出など、実務で頻出するシナリオの多くをカバーできます。

日付や時間を扱う処理を書くときは、独自計算を行う前に「timedeltaで表現できないか」を一度検討すると、バグの少ないコードにつながります。

クラウドSSLサイトシールは安心の証です。

URLをコピーしました!