毎月の請求予定は、前月の日付を一つ先へ進めれば作れそうです。ところが、1月31日から始めると、2月28日の次が3月28日になりました。僕は、こういう予定表では「毎月何日に作るか」を別に持たせます。31日指定なら、3月には31日に戻したいからです。
今日、Pythonで6か月分を試すと、31日がない月だけ月末に収まりました。
2月で消えた「31日指定」
ルールは「毎月31日。存在しない月は月末」。請求書を作る予定日のサンプルです。
最初は、前月の day と翌月の日数を比べ、小さい方を採りました。2月28日までは狙いどおり。その結果を次の入力にすると、元の31という数字がなくなります。
エラーも出ません。
| 月 | 前月の日を引き継ぐ | 指定日を保持する |
|---|---|---|
| 2026年1月 | 1月31日 | 1月31日 |
| 2月 | 2月28日 | 2月28日 |
| 3月 | 3月28日 | 3月31日 |
| 4月 | 4月28日 | 4月30日 |
| 5月 | 5月28日 | 5月31日 |
| 6月 | 6月28日 | 6月30日 |
2月だけを確認していたら、この違いは拾えませんでした。
Pythonで指定日を残して予定を作る
CPython 3.14.6で実行済みです。外部ライブラリは不要。下のコードを billing_dates.py に保存し、ターミナルで python3 billing_dates.py を実行します。
引数は開始年、開始月、指定日、月数の順で、すべて整数です。開始月を含めて数えます。
from calendar import monthrange
from datetime import date
def monthly_dates(year, month, wanted_day, count):
date(year, month, 1)
if not 1 <= wanted_day <= 31 or count < 1:
raise ValueError("指定日は1〜31、月数は1以上にしてください")
result = []
for offset in range(count):
y, m0 = divmod(year * 12 + month - 1 + offset, 12)
m = m0 + 1
last_day = monthrange(y, m)[1]
result.append(date(y, m, min(wanted_day, last_day)))
return result
schedule = monthly_dates(2026, 1, 31, 6)
assert [d.day for d in schedule] == [31, 28, 31, 30, 31, 30]
assert monthly_dates(2028, 2, 31, 1) == [date(2028, 2, 29)]
assert monthly_dates(2026, 12, 15, 2) == [
date(2026, 12, 15), date(2027, 1, 15)
]
for d in schedule:
print(d)
出力は6行。assert は結果が期待と違えば処理を止めます。
2026-01-31
2026-02-28
2026-03-31
2026-04-30
2026-05-31
2026-06-30
wanted_day はループ中に変えません。その月に使う日だけ、指定日と月の日数の小さい方へ調整します。
年月を月の通し番号にすると、年またぎも同じ式で扱えます。divmod(..., 12) の商が年、余りが0〜11の月。月を1引いて計算し、最後に1を戻します。
月の日数には標準ライブラリの calendar.monthrange() を使いました。戻り値は「月初の曜日、日数」の順なので、日数は [1]。手元の calendar.py でも、うるう年の2月は1日を足す実装を確認しました。
30日指定と月末指定は分けて考える
第3引数を30に変えると、1月30日、2月28日、3月30日になりました。月末指定なら31を渡します。
2月28日という日付だけからは、「毎月28日」と「月末」のどちらを希望したか分かりません。顧客ごとの設定には、実行済みの日付と一緒に元の指定日を残します。ここを残しておけば、3月分を手で直さずに済みます。
予定表の列名も「請求日」だけだと、作成日か送付日か迷いますね。
2026・2028・2100・2000年の各月と指定日1〜31も追加検証しました。翌月1日の前日から求めた月末と比べ、1,488件が一致しています。
土日祝日の調整は対象外です。必要なら、この関数で予定日を作った後に営業日へ調整します。その結果も、翌月の指定日には使いません。
毎月の作成予定は、元の指定日を残すところから自動化できます。会社ごとの設定が増えても、同じ関数へ渡せます。実務用に変えるときは、2月の翌月と年またぎの確認を残してください。このコードだけをコピーして使えます。
