閉じる

【Python】datetime.timedeltaで日付の差を出す(加算・減算の基本)

日付や時刻の差分や、n日後・n日前といったシフトの計算は、Python標準のdatetimeモジュールで簡単に扱えます。

本記事ではdatetime.timedeltaを中心に、日付同士の引き算、時間単位での移動、マイナスの差の扱い、そして初心者がつまずきやすい注意点まで、実行例とともに丁寧に解説します。

Pythonのdatetime.timedeltaの基本

timedeltaは日付の差と加算・減算を表す

timedeltaは「2つの日付(日時)の差」そのもの、または「日付(日時)に対して足し引きする量」を表す型です

たとえば2日と3時間を表すtimedelta(days=2, hours=3)を、datedatetimeに足し引きできます。

差分演算(datetime - datetimedate - date)の結果としてもtimedeltaが返ります。

単位は日・秒・マイクロ秒で正規化されます(hoursやminutesで与えても内部的には秒に換算されます)。

Python
from datetime import date, datetime, timedelta

# 2つの日付の差分はtimedeltaになる
d1 = date(2024, 5, 1)
d2 = date(2024, 5, 10)
delta = d2 - d1  # 2つのdateの引き算
print("差分:", delta)          # "9 days, 0:00:00"
print("日数だけ:", delta.days)  # 9

# timedeltaを作って日付に足す
shift = timedelta(days=1, hours=2, minutes=30)
print("シフト量:", shift)               # "1 day, 2:30:00"
print("属性(days, seconds, microseconds):",
      shift.days, shift.seconds, shift.microseconds)  # 1 9000 0

base = date(2024, 5, 1)
print("3日後:", base + timedelta(days=3))  # 2024-05-04
実行結果
差分: 9 days, 0:00:00
日数だけ: 9
シフト量: 1 day, 2:30:00
属性(days, seconds, microseconds): 1 9000 0
3日後: 2024-05-04

timedeltaは「どれだけずらすか」を表す部品として、差分の取得にも加算・減算にも使えるという点をまず押さえておくと理解が進みます。

使う型とimport方法(datetime, date, timedelta)

日付と時刻の基本はdatetimeモジュールに集約されています

このテーマで主に使うのはdate(日付)、datetime(日付+時刻)、timedelta(差分)の3つです。

Python
# 推奨のimport方法
from datetime import datetime, date, timedelta

以下のような役割の違いがあります。

何を表すかよく使う用途
date日付のみdate(2024, 5, 10)日単位の差分、n日後・n日前
datetime日付と時刻datetime(2024, 5, 10, 14, 30)秒や分単位の差分、時刻のシフト
timedelta差分(長さ)timedelta(days=2, hours=3)足し引きする量、差分の結果

dateは日付だけ、datetimeは時刻も含むため、同じ「差」でも結果の意味が変わる点に注意します。

日付の差を出す(2つの日時/日付の引き算)

date同士の差をdaysで取得

2つのdateの引き算はtimedeltaを返し、その.daysで日数の整数差が取れます

この日数は端点を含まない(いわゆる「経過日数」)です。

Python
from datetime import date

start = date(2024, 5, 1)
end = date(2024, 5, 10)
d = end - start
print("timedelta:", d)      # 9 days, 0:00:00
print("経過日数:", d.days)  # 9

# もし開始日と終了日を両方数え上げたい(端点を含む)なら+1する
inclusive_days = d.days + 1
print("端点を含む日数:", inclusive_days)  # 10
実行結果
timedelta: 9 days, 0:00:00
経過日数: 9
端点を含む日数: 10

「経過日数」か「端点を含む日数」かで結果は1日違うため、用途に応じて意図を明確にします。

datetime同士の差をtotal_seconds()で取得

時刻を含むdatetime同士の差はtotal_seconds()で秒単位の実数として取得できます

必要に応じて時間や分に換算してください。

Python
from datetime import datetime

dt1 = datetime(2024, 5, 1, 9, 30, 0)    # 2024-05-01 09:30:00
dt2 = datetime(2024, 5, 2, 12, 45, 30)  # 2024-05-02 12:45:30

diff = dt2 - dt1
print("差分:", diff)  # 1 day, 3:15:30

sec = diff.total_seconds()
hours = sec / 3600
print("秒:", sec)
print("時間:", hours)
実行結果
差分: 1 day, 3:15:30
秒: 98130.0
時間: 27.258333333333333

小数込みで扱いたい場合はtotal_seconds()から目的の単位へ換算するのがもっとも確実です。

マイナスの差(順序が逆のときの扱い)

早い日時から遅い日時を引くと正、逆順で引くと負のtimedeltaになります

絶対値が欲しいときはabs()を使います。

Python
from datetime import date

early = date(2024, 5, 1)
late = date(2024, 5, 10)

pos = late - early
neg = early - late

print("正の差:", pos, " days属性:", pos.days)  # 9 days, 0:00:00 / 9
print("負の差:", neg, " days属性:", neg.days)  # -9 days, 0:00:00 / -9
print("絶対値:", abs(neg), " days属性:", abs(neg).days)  # 9 days, 0:00:00 / 9
実行結果
正の差: 9 days, 0:00:00  days属性: 9
負の差: -9 days, 0:00:00  days属性: -9
絶対値: 9 days, 0:00:00  days属性: 9

符号付きの差が意味を持つ処理(締切が過ぎたか等)では、負の差をそのまま使うと意図が明確になります。

日付の加算・減算(timedeltaを足す/引く)

n日後・n日前を求める

n日後やn日前はdatetimedelta(days=n)を足し引きします

同じ要領でdatetimeにも使えます。

Python
from datetime import date, timedelta

base = date(2024, 8, 1)
print("基準日:", base)

print("7日後:", base + timedelta(days=7))   # 2024-08-08
print("7日前:", base - timedelta(days=7))   # 2024-07-25
実行結果
基準日: 2024-08-01
7日後: 2024-08-08
7日前: 2024-07-25

「日」単位の移動はtimedelta(days=...)でシンプルに表現できます

時間や分のシフト(hours, minutes)

時間や分のシフトはtimedelta(hours=..., minutes=...)で表現します

日付をまたぐ場合も自動で繰り上がり(繰り下がり)ます。

Python
from datetime import datetime, timedelta

dt = datetime(2024, 8, 1, 9, 0, 0)  # 2024-08-01 09:00:00
print("基準:", dt)

print("3時間15分後:", dt + timedelta(hours=3, minutes=15))  # 12:15
print("10時間前:", dt - timedelta(hours=10))               # 前日の23:00
print("1日と2時間後:", dt + timedelta(days=1, hours=2))     # 翌日11:00
実行結果
基準: 2024-08-01 09:00:00
3時間15分後: 2024-08-01 12:15:00
10時間前: 2024-07-31 23:00:00
1日と2時間後: 2024-08-02 11:00:00

単位を混ぜてもOKで、内部では日・秒・マイクロ秒に正規化されます

1日ずつ進める/戻す(forループで利用)

開始日から終了日までを1日ずつループする典型パターンは、差分の日数をrangeに渡す方法です

端点を含めたい場合は+1します。

Python
from datetime import date, timedelta

start = date(2024, 8, 1)
end = date(2024, 8, 4)  # 端点を含めたい

days = (end - start).days + 1
for i in range(days):
    current = start + timedelta(days=i)
    print(current)
実行結果
2024-08-01
2024-08-02
2024-08-03
2024-08-04

ループでは「何日ずらすか」をインデックスにしてstart + timedelta(days=i)とすると直感的です

初心者が知っておきたい注意点

月や年の加算は不可(timedeltaは日以下のみ)

timedeltaは月や年の加算を直接表せません

月は28〜31日と可変、年もうるう年があるため、timedelta(days=30)のような近似は正しくないことがあります。

Python
from datetime import date, timedelta

d = date(2024, 1, 31)  # うるう年
print("基準日:", d)
print("31日後(本当に1か月後?):", d + timedelta(days=31))  # -> 2024-03-02
実行結果
基準日: 2024-01-31
31日後(本当に1か月後?): 2024-03-02

「1か月後」を厳密に扱うにはtimedeltaではなく「月」概念が必要です。

標準ライブラリだけで近いことをしたい場合は、次のような補助関数を自作します(端末日を越える場合は月末に丸めます)。

Python
from datetime import date
from calendar import monthrange

def add_months(d: date, months: int) -> date:
    """dにmonthsか月を加算(負も可)。日が月末を超えるときは月末に丸める。"""
    y = d.year + (d.month - 1 + months) // 12
    m = (d.month - 1 + months) % 12 + 1
    last_day = monthrange(y, m)[1]
    day = min(d.day, last_day)
    return date(y, m, day)

# 例
print(add_months(date(2024, 1, 31), 1))   # 2024-02-29
print(add_months(date(2024, 1, 31), 2))   # 2024-03-31
print(add_months(date(2024, 3, 31), -1))  # 2024-02-29
実行結果
2024-02-29
2024-03-31
2024-02-29

厳密な「月」「年」加算はtimedeltaの守備範囲外という前提を忘れないでください。

dateとdatetimeの違い(時間を含むかで結果が変わる)

dateは日付のみ、datetimeは時刻も含むため、差の意味と結果が変わります

またdateとdatetimeを直接引き算することはできません

Python
from datetime import datetime, date, time

dt = datetime(2024, 5, 1, 12, 0, 0)  # 正午
d = date(2024, 5, 1)

# 直接引くとTypeError
try:
    _ = dt - d
except TypeError as e:
    print("TypeError:", e)

# dateをdatetime(0時)に変換してから引く
dt_from_date = datetime.combine(d, time())
print("差分:", dt - dt_from_date)             # 12:00:00
print("秒:", (dt - dt_from_date).total_seconds())  # 43200.0

# date同士での差(時刻は考慮しない)
d2 = date(2024, 5, 2)
print("date同士の差(日数):", (d2 - d).days)   # 1
実行結果
TypeError: unsupported operand type(s) for -: 'datetime.datetime' and 'datetime.date'
差分: 12:00:00
秒: 43200.0
date同士の差(日数): 1

「日だけを見たい」のか「時刻まで見たい」のかを先に決め、適切にdatedatetimeへ寄せるのがコツです。

端数の扱い(daysとtotal_seconds()の使い分け)

timedelta.daysは整数日数の成分だけで、端数は切り捨てられます

小数を含めて「正確な日数」を知りたいならtotal_seconds() / 86400を使います。

負の差では特に挙動の理解が重要です。

Python
from datetime import datetime

# 正の差: 1.5日
diff = datetime(2024, 5, 2, 12, 0, 0) - datetime(2024, 5, 1, 0, 0, 0)
print("表示:", diff)                        # 1 day, 12:00:00
print("days属性(整数):", diff.days)         # 1
print("正確な日数:", diff.total_seconds() / 86400)  # 1.5

# 負の差: -36時間 (= -1.5日)
neg = datetime(2024, 5, 1, 0, 0, 0) - datetime(2024, 5, 2, 12, 0, 0)
print("表示:", neg)                         # 表示は環境により "-1 day, 12:00:00" 等
print("days属性:", neg.days)               # -2 (成分としての「日」は切下げ)
print("seconds属性:", neg.seconds)         # 43200 (正の秒に正規化される)
print("正確な日数:", neg.total_seconds() / 86400)  # -1.5
実行結果
表示: 1 day, 12:00:00
days属性(整数): 1
正確な日数: 1.5
表示: -1 day, 12:00:00
days属性: -2
seconds属性: 43200
正確な日数: -1.5

「整数日だけで良い」なら.days、「端数込みの実時間」が必要ならtotal_seconds()から換算すると覚えてください。

丸めたい場合はmath.floormath.ceilの使い分けを検討します。

欲しい値ごとの選び方は次のとおりです。

欲しいもの使う式備考
経過日数(整数、端点非含)(end - start).days時刻は切り捨てられる
端点を含む日数(end - start).days + 1end >= start のとき
実際の時間差(秒)(dt2 - dt1).total_seconds()浮動小数で取得
実際の時間差(日)(dt2 - dt1).total_seconds() / 86400端数込み

タイムゾーンや夏時間(DST)を扱う場合はzoneinfoで「 aware なdatetime」を使う必要があります(本稿では割愛)。

まとめ

datetime.timedeltaは「差分」と「シフト」の両方を一手に担う、日付時刻計算の基礎ツールです

日付同士の引き算は.daysで整数日を、時刻まで含む差はtotal_seconds()で正確に扱うのが基本でした。

n日後・n日前、時間や分のシフト、1日ずつのループなど、実務でよく使う操作はすべてtimedeltaで表現できます。

一方で月や年の加算はtimedeltaでは扱えない点、datedatetimeの混在や端数の扱いで誤解が起きやすい点には注意が必要です。

まずはここで学んだ基本を確実に押さえ、要件に応じて整数日なら.days、実時間ならtotal_seconds()という指針で実装してみてください。

この記事を書いた人
エーテリア編集部
エーテリア編集部

人気のPythonを初めて学ぶ方向けに、文法の基本から小さな自動化まで、実際に手を動かして理解できる記事を書いています。

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

URLをコピーしました!