日付や時刻の差分や、n日後・n日前といったシフトの計算は、Python標準のdatetimeモジュールで簡単に扱えます。
本記事ではdatetime.timedelta
を中心に、日付同士の引き算、時間単位での移動、マイナスの差の扱い、そして初心者がつまずきやすい注意点まで、実行例とともに丁寧に解説します。
Pythonのdatetime.timedeltaの基本
timedeltaは日付の差と加算・減算を表す
timedeltaは「2つの日付(日時)の差」そのもの、または「日付(日時)に対して足し引きする量」を表す型です。
たとえば2日と3時間を表すtimedelta(days=2, hours=3)
を、date
やdatetime
に足し引きできます。
差分演算(datetime - datetime
やdate - date
)の結果としてもtimedelta
が返ります。
単位は日・秒・マイクロ秒で正規化されます(hoursやminutesで与えても内部的には秒に換算されます)。
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つです。
# 推奨の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
で日数の整数差が取れます。
この日数は端点を含まない(いわゆる「経過日数」)です。
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()
で秒単位の実数として取得できます。
必要に応じて時間や分に換算してください。
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()
を使います。
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日前はdate
にtimedelta(days=n)
を足し引きします。
同じ要領でdatetime
にも使えます。
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=...)
で表現します。
日付をまたぐ場合も自動で繰り上がり(繰り下がり)ます。
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します。
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)
のような近似は正しくないことがあります。
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
ではなく「月」概念が必要です。
標準ライブラリだけで近いことをしたい場合は、次のような補助関数を自作します(端末日を越える場合は月末に丸めます)。
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を直接引き算することはできません。
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
「日だけを見たい」のか「時刻まで見たい」のかを先に決め、適切にdate
かdatetime
へ寄せるのがコツです。
端数の扱い(daysとtotal_seconds()の使い分け)
timedelta.days
は整数日数の成分だけで、端数は切り捨てられます。
小数を含めて「正確な日数」を知りたいならtotal_seconds() / 86400
を使います。
負の差では特に挙動の理解が重要です。
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.floor
やmath.ceil
の使い分けを検討します。
欲しい値ごとの選び方は次のとおりです。
欲しいもの | 使う式 | 備考 |
---|---|---|
経過日数(整数、端点非含) | (end - start).days | 時刻は切り捨てられる |
端点を含む日数 | (end - start).days + 1 | end >= start のとき |
実際の時間差(秒) | (dt2 - dt1).total_seconds() | 浮動小数で取得 |
実際の時間差(日) | (dt2 - dt1).total_seconds() / 86400 | 端数込み |
タイムゾーンや夏時間(DST)を扱う場合はzoneinfo
で「 aware なdatetime
」を使う必要があります(本稿では割愛)。
まとめ
datetime.timedeltaは「差分」と「シフト」の両方を一手に担う、日付時刻計算の基礎ツールです。
日付同士の引き算は.days
で整数日を、時刻まで含む差はtotal_seconds()
で正確に扱うのが基本でした。
n日後・n日前、時間や分のシフト、1日ずつのループなど、実務でよく使う操作はすべてtimedelta
で表現できます。
一方で月や年の加算はtimedelta
では扱えない点、date
とdatetime
の混在や端数の扱いで誤解が起きやすい点には注意が必要です。
まずはここで学んだ基本を確実に押さえ、要件に応じて整数日なら.days
、実時間ならtotal_seconds()
という指針で実装してみてください。