Python で「来月の第2月曜日」など N 番目の曜日を取得する

「毎月第2月曜日に会議」「第3金曜日がリリース日」といったスケジュールは、カレンダーアプリや業務システムでよく見かけます。Python で N 番目の曜日を計算する方法を解説します。

考え方

「第2月曜日」を求めるには、まず月初から最初の月曜日を見つけ、そこから7日を足せば2番目の月曜日になります。一般化すると、N 番目の曜日は「最初のその曜日 + 7 × (N - 1) 日」で計算できます。

基本の実装

特定の月の N 番目の曜日を取得する関数を作ってみましょう。

from datetime import date, timedelta

def nth_weekday(year, month, weekday, n):
    """
    指定した月の N 番目の曜日を取得
    weekday: 0=月, 1=火, 2=水, 3=木, 4=金, 5=土, 6=日
    n: 何番目か(1〜5)
    """
    # 月初
    first = date(year, month, 1)
    
    # 月初から最初のその曜日までの日数
    days_until = (weekday - first.weekday()) % 7
    first_weekday = first + timedelta(days=days_until)
    
    # N 番目なので (N-1) 週分足す
    result = first_weekday + timedelta(weeks=n - 1)
    
    # 月をまたいでいたら None を返す
    if result.month != month:
        return None
    
    return result

# 2025年1月の第2月曜日
print(nth_weekday(2025, 1, 0, 2))  # 2025-01-13

# 2025年1月の第3金曜日
print(nth_weekday(2025, 1, 4, 3))  # 2025-01-17

(weekday - first.weekday()) % 7 の部分がポイントです。月初の曜日と目的の曜日の差を計算し、負の値にならないように 7 で剰余を取っています。

曜日を文字列で指定する

曜日を数字で指定するのは分かりにくいので、文字列で指定できるようにしてみます。

from datetime import date, timedelta

WEEKDAYS = {
    "": 0, "": 1, "": 2, "": 3, "": 4, "": 5, "": 6,
    "mon": 0, "tue": 1, "wed": 2, "thu": 3, "fri": 4, "sat": 5, "sun": 6,
}

def nth_weekday(year, month, weekday, n):
    if isinstance(weekday, str):
        weekday = WEEKDAYS[weekday.lower()]
    
    first = date(year, month, 1)
    days_until = (weekday - first.weekday()) % 7
    first_weekday = first + timedelta(days=days_until)
    result = first_weekday + timedelta(weeks=n - 1)
    
    if result.month != month:
        return None
    return result

print(nth_weekday(2025, 1, "", 2))  # 2025-01-13
print(nth_weekday(2025, 1, "fri", 3))  # 2025-01-17

祝日の例:成人の日

成人の日は「1月の第2月曜日」と定められています。

def seijin_no_hi(year):
    """成人の日を取得"""
    return nth_weekday(year, 1, 0, 2)

for year in range(2025, 2030):
    print(f"{year}年: {seijin_no_hi(year)}")

出力:

# 2025年: 2025-01-13
# 2026年: 2026-01-12
# 2027年: 2027-01-11
# 2028年: 2028-01-10
# 2029年: 2029-01-08

月末から数える

「最終金曜日」のように月末から数えたい場合は、別のアプローチが必要です。

from datetime import date, timedelta
import calendar

def last_weekday(year, month, weekday):
    """指定した月の最後のその曜日を取得"""
    # 月末日を取得
    last_day = calendar.monthrange(year, month)[1]
    last = date(year, month, last_day)
    
    # 月末から遡って最初のその曜日
    days_back = (last.weekday() - weekday) % 7
    return last - timedelta(days=days_back)

# 2025年1月の最終金曜日
print(last_weekday(2025, 1, 4))  # 2025-01-31

プレミアムフライデーの計算などに使えますね。

calendar モジュールを使う方法

calendar モジュールにも同様の機能があります。

import calendar

# 2025年1月のカレンダーを行列で取得
cal = calendar.monthcalendar(2025, 1)

# 第2月曜日(各行の [0] が月曜日)
second_monday = [week[0] for week in cal if week[0] != 0][1]
print(second_monday)  # 13

ただし日付だけが返るため、date オブジェクトが欲しい場合は自前の関数のほうが使いやすいでしょう。