きり丸の技術日記

技術検証したり、資格等をここに残していきます。

Pythonの標準ライブラリで先月今月来月の月初月末を求める(datetime, calendar)

Pythonの標準ライブラリを使って、先月今月来月の月初月末を求める方法がわからなかったので、記事にします。

なお、サードパーティ製のライブラリdateutilを使ったほうが楽に導き出せるようですが、この記事では標準ライブラリにこだわることにします。

環境

  • Python
    • 3.8.6
  • pytest
    • 5.4.3

使用する標準ライブラリ

  • datetime
  • calendar
import datetime 
import calendar

基準

次の日付を基準にします。

now = datetime.datetime(2020, 2, 15, 20, 29, 39)

今月の月初

replace(day=1)で日付を1日に変更します。

assert now.replace(day=1) == datetime.datetime(2020,2,1,20,29,39)

今月の月末

calender#monthrange()を使用します。対象の年と月をパラメータに渡し、配列の1番目を取得すると月末を取得することができます。うるう年も考慮して値を返却してくれます。

end_day = calendar.monthrange(now.year, now.month)[1]
assert now.replace(day=end_day) == datetime.datetime(2020,2,29,20,29,39)

なお、配列の0番目は月初ではありません。1日の曜日となります。ヨーロッパの慣例に従った月曜日を週の始まり(0)とし、日曜日を最後の日(6)とした値が返却されます。2020/02/01は土曜日ですので、5が返却されます。

assert calendar.monthrange(now.year, now.month) == (5, 29)
  
>>>print(calendar.month(2020, 2))
   February 2020
Mo Tu We Th Fr Sa Su
                1  2
 3  4  5  6  7  8  9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29

先月と来月

replace(month=X, day=1)で指定した月の月初を求めることができます。

# 先月
assert now.replace(month=now.month-1 ,day=1) == datetime.datetime(2020,1,1,20,29,39)
# 今月
assert now.replace(month=now.month+1 ,day=1) == datetime.datetime(2020,3,1,20,29,39)

ただし、月は1-12の期間のみしか入力できないため、年跨ぎを行おうとするとエラーになります。

try:
    assert now.replace(month=13)
    assert False
except ValueError:
    assert True

もし、安全に処理したい場合は、1日にreplaceした後に-1日を行い、再度1日にreplaceすることで先月の月初を求められます。

last_month = (now.replace(day=1) - datetime.timedelta(days=1)).replace(day=1)
assert last_month == datetime.datetime(2020,1,1,20,29,39)

来月の月初も同様です。月末日にreplaceした後に、+1日をすることで来月末の月初を求められます。

max_day = calendar.monthrange(now.year, now.month)[1]
next_month = now.replace(day=max_day) + datetime.timedelta(days=1)

assert next_month == datetime.datetime(2020,3,1,20,29,39)

備考

先月、来月の前後1ヵ月に関しては安全に処理できます。しかし、前後2ヵ月以上の計算は標準ライブラリでは処理が面倒です。おそらく、この点においてサードパーティのdateutilを使用するのでしょう。

標準ライブラリで1ヵ月加算することが簡単だったらよかったのですが、datetime.timedeltaのリファレンスを参照する限り難しそうです。

# monthsが欲しかった…。
datetime.timedelta(
  days=0, 
  seconds=0, 
  microseconds=0, 
  milliseconds=0, 
  minutes=0, 
  hours=0, 
  weeks=0
)

ソースコード

終わりに

Pythonがまだ不慣れだということもあり、簡単に計算できると思っていたことが簡単には処理できませんでした。

というか、日付計算にadd(day=0)みたいなメソッドがあると思っていたのですが、それがないのが個人的に不満ポイント。日付計算にdatetime.timedelta(day=0)が必要だということが分かって、直感的にコーディングできると言われているPythonであればもうちょっと簡単な方法があるのではないかと疑ってしまいました。

addとかだとインスタンスが可変に見えるから導入しない、とかそういう理由ですかね…。

手間取ってしまいましたが、違う言語を学ぶことはいい経験になりますね。他の言語も学んで色んな考え方を吸収したいです。


この記事がお役に立ちましたら、各種SNSでのシェアや、今後も情報発信しますのでフォローよろしくお願いします。

参考

f:id:nainaistar:20210707002516p:plain