【Python】dateutil relativedeltaで年月の相対変化値を取得する場合の注意点
概要
Pythonで過去のデータに対する集計などを行う際に昨年、先月などの単位で集計したい時があると思います。このような時にはdateutilライブラリを使うと楽に実現できておすすめなのですが、コードを間違えると思わぬ結果が返ってくるため、実例を踏まえて紹介します。
検証環境
python 3.8.10
使用するライブラリ
datetimeとdateutilを使用します。
import datetime
from dateutil.relativedelta import relativedelta
dateutilのrelativedeltaを使用することで月や年の相対変化値をとることができます。例えば一年前や一か月後の日付などを取得することができます。
こちらに詳しく使い方が載っています。
relativedelta — dateutil 2.8.2 documentation
注意点1:relativedeltaの引数
以下は一か月後のコードを取得する場合の例です。
OK例
from dateutil.relativedelta import relativedelta
import datetime
today = datetime.date.today()
last_month_day = today - relativedelta(months=1)
print('today:',today)
print('last month on this day:',last_month_day)
today: 2022-03-07 last month on this day: 2022-02-07
NG例
次に一か月後を取得する目的では間違っているコードの例です
today = datetime.date.today()
last_month_day = today - relativedelta(month=1)
print('today:',today)
print('last month on this day:',last_month_day)
today: 2022-03-07 last month on this day: 2022-01-07
間違いポイントとしては ” relativedelta(month=1) ” の部分です。引数でmonth=1にしてしまっていますが、正しくは”months=1″です。sが要ります。
monthにしてしまった場合、そのまま月の値として1が設定されてしまい、結果が1月になってしまったというわけです。
なぜこれが注意点かというと、エラーを吐かずにそのまま通ってしまうためです。このようなrelativedeltaの性質を頭に入れておかないと気付かないうちにバグを生んでしまう可能性があります。
この注意点についてはyearについても同じです。yearsをyearにしてしまうと年に”1″が設定されてしまいます。
NG例
from dateutil.relativedelta import relativedelta
import datetime
today = datetime.date.today()
last_year_day = today - relativedelta(year=1)
print('today:',today)
print('last year on this day:',last_year_day)
today: 2022-03-07 last year on this day: 0001-03-07
注意点2:相対変化月に同日が無い場合の挙動
例えば今月に31日まであり、先月は28日までしかないような場合は丸められてすべて先月の最後の日付になります。
print(datetime.date(2022,3,31) - relativedelta(months=1))
print(datetime.date(2022,3,30) - relativedelta(months=1))
print(datetime.date(2022,3,29) - relativedelta(months=1))
2022-02-28 2022-02-28 2022-02-28
そのため、一か月前にしてから一か月後に戻す操作をすると再現性が無い場合があるため注意が必要です。例えば以下のような場合です。
original_day = datetime.date(2022,3,31)
last_month_day = today - relativedelta(months=1)
not_original_day = last_month_day + relativedelta(months=1)
print('original day:',original_day)
print('last month on this day:',last_month_day)
print('not original day:',not_original_day)
original day: 2022-03-31 last month on this day: 2022-02-28 not original day: 2022-03-28
3月31日から2月28日になり、その後3月28日になっています。月の相対だけで考えると3月31になってほしい気もしますがなりません。そこは厳密です。
まとめ
dateutil.relativedeltaは非常に便利ですが、落とし穴的なところがいくつかあるため注意点が必要。特にエラーを吐かないためあらかじめこのような特性は頭の片隅に入れておきたい。