Pythonで月初や月末、週初めの日付を求める方法

今回もPythonの日付操作に関する話です。稀に必要になる月初や月末日付、週の開始日の日付を得る方法を紹介します。

まず、月初からです。まず、ある日付を含む月の月初の日付(要するに1日)を取得したい場合、必要なのは月初の日付を指し示すdateやdatetimeオブジェクトなのか、その日付を表す文字列なのかを考える必要があります。もし、文字列で必要なのであれば、一番簡単な方法は strftimeで’%Y-%m-01’などのフォーマットに指定することです。普通なら%dとする部分を、01にして決め打ちしてるだけですね。

strftimeについては、こちらの記事でも取り扱っています。
参考: pythonで今日の日付を表す文字列をつくる

具体的には次のようになります。

from datetime import datetime


dt1 = datetime(2021, 11, 15)
print(dt1)
# 2021-11-15 00:00:00

# strftime でその日を含む月の1日を示す文字列を得る
print(dt1.strftime("%Y-%m-01"))
# 2021-11-01

いや、日付の文字列ではなく、その月の初日を示すdatetimeオブジェクトが欲しいんだ!という場合、 dateやdatetime オブジェクトが replace というメソッドを持っているので、day=1 と渡してあげると日付部分を書き換えることができます。
参考: datetime.replace

from datetime import datetime
from datetime import date


dt1 = datetime(2021, 11, 15, 11, 0, 0)
print(dt1)
# 2021-11-15 11:00:00

date1 = date(2021, 11, 15)
print(date1)
# 2021-11-15

# replaceで 日付部分を1に書き換える
print(dt1.replace(day=1))
# 2021-11-01 11:00:00

print(date1.replace(day=1))
# 2021-11-01

日付の加算の記事の最後の方で紹介した、relativedelta において、 day=1 (days=1ではないので注意)と引数を渡すと、日付を1に置き換える動きになる、という仕様がありましたが、これを使って実現することもできます。
ただし、datetimeモジュールだけで実現できる捜査を行うのにわざわざ別のライブラリをインポートするメリットはないのでここではコードの実行例は省略します。
参考: Pythonで日付の加算、特にnヶ月後やn年後の日付を求める方法

さて、これで月初の日付を得る方法は得られました。次は月末の日付を得る方法です。

月初の日付は常に1日でしたが、月末の日付は月によって違うので、月初のように書式設定やreplaceで得るのはちょっと面倒です。そこで、月末の日付が欲しい場合は、まずその月の月初の日付を求め、それに1ヶ月足し、さらにその前日を求めるという手順を踏んでいきます。

3ステップもあって面倒だ、と思われるかもしれませんが、relativedelta が実は非常に便利な仕様を持っています。
参考: relativedelta — dateutil 2.8.2 documentation

一部引用します。
There are relative and absolute forms of the keyword arguments. The plural is relative, and the singular is absolute. For each argument in the order below, the absolute form is applied first (by setting each attribute to that value) and then the relative form (by adding the value to the attribute).

要するに、absolute(複数形のつかない置換の引数)が優先されるってことですね。
また、この引用文の直下に書かれていますが、演算は年から始まり、マイクロ秒に向かって大きい順に行われます。

これにより、relativedelta(day=1, months=1, days=-1) とすると、日付を1に置き換えて、1ヶ月足して、1日引くという望んでた処理を行ってくれることになります。

from dateutil.relativedelta import relativedelta

date2 = date(2021, 11, 15)
print(date2)
# 2021-11-15

print(date2 + relativedelta(day=1, months=1, days=-1))
# 2021-11-30

これで月末日付が得られました。

最後にこれはついでになってしまうのですが、週初め(その日付を含む週の月曜日)の日付を計算する方法も書いておきます。これがPandasのDataFrameに入ってるデータだったら以前紹介した方法が使えるのでこちらを見てください。
参考: pandasの日付データを週単位で丸める(to_periodを使う方法)

今回は単体のdateオブジェクトに対する方法です。

これはやり方はいろいろあると思うのですが、個人的にはdateオブジェクトが持っている、weekday()メソッド(月〜日の曜日を0から6の数値で得るメソッド)を使って、これで返ってきた数値分の日数を引くのがいいと思っています。
要するに次のコードのような感じです。

from datetime import timedelta


date2 = date(2021, 11, 17)  # 2021/11/17 は水曜日
print(date2)
# 2021-11-17

print(date2.weekday())  # 水曜なので2が返る
# 2

# weekday() 分の日数を引くと月曜の日付が得られる
print(date2 - timedelta(days=date2.weekday()))
# 2021-11-15

relativedelta にも weekday っていう引数を渡すことができ、これに0を渡すと月曜の日付を返してはくれるのですが、元の日付が月曜日ならその日のまま、月曜以外なら「次の」月曜の日付が帰ってくるんですよね。この仕様が使いやすい場面もあるのかもしれませんが個人的にはいまいちです。

print(date2)
# 2021-11-17

# 月曜以外の日付に対しては、次の月曜が返ってくる
print(date2 + relativedelta(weekday=0))
# 2021-11-22

date3 = date(2021, 11, 8)  # 2021/11/8は月曜
# 月曜の日付に対しては、その日のまま
print(date3 + relativedelta(weekday=0))
# 2021-11-08

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です