0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Pythonで請求予定日を作る、2月の28日を翌月へ引きずらない

0
Posted at

毎月の請求予定は、前月の日付を一つ先へ進めれば作れそうです。ところが、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 はループ中に変えません。その月に使う日だけ、指定日と月の日数の小さい方へ調整します。

指定日31日を固定し、2月は28日、3月は31日を別々に計算する流れ。2月の結果で元の指定日を上書きしない。

年月を月の通し番号にすると、年またぎも同じ式で扱えます。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月の翌月と年またぎの確認を残してください。このコードだけをコピーして使えます。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?