Python第何何曜日(第2月曜日など)の日付を取得する方法【完全解説】

 

Pythonで「第2月曜日」「第3金曜日」などの第N曜日の日付を取得する方法を詳しく解説します。基本的な実装から祝日対応まで、実用的なサンプルコードとともに学びましょう。

基本的な第N曜日の取得

基本実装

from datetime import date, timedelta
import calendar

def get_nth_weekday(year, month, weekday, n):
    """指定月の第N曜日を取得
    
    Args:
        year: 年
        month: 月
        weekday: 曜日 (0=月曜日, 1=火曜日, ..., 6=日曜日)
        n: 第何曜日か (1, 2, 3, 4, 5)
    
    Returns:
        date: 該当日付、存在しない場合はNone
    """
    # 月の1日を取得
    first_day = date(year, month, 1)
    
    # 1日から指定曜日までの日数を計算
    days_until_target = (weekday - first_day.weekday()) % 7
    
    # 第1指定曜日の日付
    first_target = first_day + timedelta(days=days_until_target)
    
    # 第N指定曜日の日付
    target_date = first_target + timedelta(weeks=n-1)
    
    # 同じ月内かチェック
    if target_date.month == month and target_date.year == year:
        return target_date
    else:
        return None

# 使用例
year = 2024
month = 1

# 各種第N曜日を取得
weekday_names = ['月', '火', '水', '木', '金', '土', '日']

print(f"=== {year}年{month}月の第N曜日 ===")
for weekday_index, weekday_name in enumerate(weekday_names):
    print(f"\n{weekday_name}曜日:")
    for n in range(1, 6):
        nth_date = get_nth_weekday(year, month, weekday_index, n)
        if nth_date:
            print(f"  第{n}{weekday_name}曜日: {nth_date}")
        else:
            print(f"  第{n}{weekday_name}曜日: なし")

より詳細な情報を含む実装

def get_nth_weekday_detailed(year, month, weekday, n):
    """第N曜日の詳細情報を取得"""
    target_date = get_nth_weekday(year, month, weekday, n)
    
    if target_date is None:
        return None
    
    # 月内での順序を計算
    month_calendar = calendar.monthcalendar(year, month)
    week_number = None
    day_in_week = None
    
    for week_idx, week in enumerate(month_calendar):
        if target_date.day in week:
            week_number = week_idx + 1
            day_in_week = week.index(target_date.day)
            break
    
    weekday_names = ['月', '火', '水', '木', '金', '土', '日']
    
    return {
        'date': target_date,
        'weekday_name': weekday_names[weekday],
        'nth': n,
        'week_of_month': week_number,
        'day_of_month': target_date.day,
        'is_month_end': target_date.day > 25  # 月末に近いかの目安
    }

# 使用例
detailed_info = get_nth_weekday_detailed(2024, 1, 0, 2)  # 第2月曜日
if detailed_info:
    print(f"第{detailed_info['nth']}{detailed_info['weekday_name']}曜日: {detailed_info['date']}")
    print(f"月の第{detailed_info['week_of_month']}週目")
    print(f"月の{detailed_info['day_of_month']}日目")

複数月・複数年の第N曜日取得

年間の第N曜日一覧

def get_yearly_nth_weekdays(year, weekday, n):
    """年間の第N曜日を一覧取得"""
    yearly_dates = []
    weekday_names = ['月', '火', '水', '木', '金', '土', '日']
    
    for month in range(1, 13):
        nth_date = get_nth_weekday(year, month, weekday, n)
        
        month_info = {
            'year': year,
            'month': month,
            'date': nth_date,
            'exists': nth_date is not None
        }
        
        if nth_date:
            month_info.update({
                'day': nth_date.day,
                'weekday_name': weekday_names[weekday],
                'nth': n
            })
        
        yearly_dates.append(month_info)
    
    return yearly_dates

# 使用例:2024年の第2金曜日
second_fridays = get_yearly_nth_weekdays(2024, 4, 2)  # 4=金曜日

print("=== 2024年 各月の第2金曜日 ===")
for month_data in second_fridays:
    if month_data['exists']:
        print(f"{month_data['month']:2d}月: {month_data['date']} ({month_data['day']:2d}日)")
    else:
        print(f"{month_data['month']:2d}月: なし")

複数年間の比較

def compare_nth_weekdays_across_years(years, weekday, n):
    """複数年間の第N曜日を比較"""
    comparison_data = []
    
    for year in years:
        year_data = get_yearly_nth_weekdays(year, weekday, n)
        comparison_data.append({
            'year': year,
            'months': year_data,
            'total_occurrences': sum(1 for m in year_data if m['exists']),
            'missing_months': [m['month'] for m in year_data if not m['exists']]
        })
    
    return comparison_data

# 使用例:複数年の第5金曜日比較
years = [2023, 2024, 2025]
fifth_fridays_comparison = compare_nth_weekdays_across_years(years, 4, 5)

print("=== 複数年の第5金曜日比較 ===")
for year_data in fifth_fridays_comparison:
    print(f"{year_data['year']}年: {year_data['total_occurrences']}回発生")
    if year_data['missing_months']:
        print(f"  発生しない月: {year_data['missing_months']}")

特定パターンの第N曜日

月の最初と最後の指定曜日

def get_first_and_last_weekday(year, month, weekday):
    """月の最初と最後の指定曜日を取得"""
    first_weekday = get_nth_weekday(year, month, weekday, 1)
    
    # 最後の指定曜日を取得
    last_weekday = None
    for n in range(5, 0, -1):  # 5から1まで逆順で検索
        candidate = get_nth_weekday(year, month, weekday, n)
        if candidate:
            last_weekday = candidate
            break
    
    return {
        'first': first_weekday,
        'last': last_weekday,
        'count': len([get_nth_weekday(year, month, weekday, n) 
                     for n in range(1, 6) 
                     if get_nth_weekday(year, month, weekday, n) is not None])
    }

# 使用例
year = 2024
month = 1
weekday = 0  # 月曜日

first_last = get_first_and_last_weekday(year, month, weekday)
print(f"{year}年{month}月の月曜日:")
print(f"最初: {first_last['first']}")
print(f"最後: {first_last['last']}")
print(f"総数: {first_last['count']}回")

偶数・奇数週の曜日

def get_even_odd_weekdays(year, month, weekday, parity='even'):
    """偶数週または奇数週の指定曜日を取得
    
    Args:
        parity: 'even' (偶数週) または 'odd' (奇数週)
    """
    weekdays = []
    
    for n in range(1, 6):
        nth_date = get_nth_weekday(year, month, weekday, n)
        if nth_date:
            if parity == 'even' and n % 2 == 0:
                weekdays.append({'date': nth_date, 'nth': n})
            elif parity == 'odd' and n % 2 == 1:
                weekdays.append({'date': nth_date, 'nth': n})
    
    return weekdays

# 使用例
even_mondays = get_even_odd_weekdays(2024, 1, 0, 'even')
odd_mondays = get_even_odd_weekdays(2024, 1, 0, 'odd')

print("2024年1月の月曜日:")
print("偶数週:", [d['date'] for d in even_mondays])
print("奇数週:", [d['date'] for d in odd_mondays])

祝日・営業日を考慮した第N曜日

営業日としての第N曜日

def get_nth_business_weekday(year, month, n, holidays=None):
    """第N営業日を取得(土日祝除く)"""
    if holidays is None:
        holidays = set()
    else:
        holidays = set(holidays)
    
    current_date = date(year, month, 1)
    _, last_day = calendar.monthrange(year, month)
    month_end = date(year, month, last_day)
    
    business_day_count = 0
    
    while current_date <= month_end:
        # 平日かつ祝日でない場合
        if current_date.weekday() < 5 and current_date not in holidays:
            business_day_count += 1
            if business_day_count == n:
                return current_date
        
        current_date += timedelta(days=1)
    
    return None

# 使用例
# 簡略化した祝日リスト
holidays_2024 = [
    date(2024, 1, 1),   # 元日
    date(2024, 2, 11),  # 建国記念の日
    date(2024, 4, 29),  # 昭和の日
]

print("2024年1月の営業日:")
for n in range(1, 11):
    business_day = get_nth_business_weekday(2024, 1, n, holidays_2024)
    if business_day:
        weekday_name = ['月', '火', '水', '木', '金', '土', '日'][business_day.weekday()]
        print(f"第{n:2d}営業日: {business_day} ({weekday_name})")

祝日の振替を考慮した第N曜日

def get_nth_weekday_with_holiday_shift(year, month, weekday, n, holidays=None):
    """祝日の場合は次の営業日に振替える第N曜日"""
    target_date = get_nth_weekday(year, month, weekday, n)
    
    if target_date is None:
        return None
    
    if holidays is None:
        holidays = set()
    else:
        holidays = set(holidays)
    
    # 祝日または週末の場合は次の営業日を探す
    while (target_date.weekday() >= 5 or  # 週末
           target_date in holidays):       # 祝日
        target_date += timedelta(days=1)
        
        # 月を超えた場合は処理を停止
        if target_date.month != month:
            return None
    
    return target_date

# 使用例
original_date = get_nth_weekday(2024, 2, 0, 2)  # 第2月曜日
shifted_date = get_nth_weekday_with_holiday_shift(2024, 2, 0, 2, holidays_2024)

print(f"2024年2月第2月曜日:")
print(f"元の日付: {original_date}")
print(f"振替後: {shifted_date}")

実用的な応用例

定期会議のスケジュール生成

class MeetingScheduler:
    def __init__(self, holidays=None):
        self.holidays = set(holidays) if holidays else set()
        self.weekday_names = ['月', '火', '水', '木', '金', '土', '日']
    
    def schedule_regular_meeting(self, year, weekday, nth, months=None):
        """定期会議のスケジュールを生成"""
        if months is None:
            months = range(1, 13)
        
        schedule = []
        
        for month in months:
            meeting_date = get_nth_weekday(year, month, weekday, nth)
            
            if meeting_date:
                # 祝日チェック
                is_holiday = meeting_date in self.holidays
                
                # 振替日の計算
                if is_holiday or meeting_date.weekday() >= 5:
                    alternative_date = self._find_alternative_date(meeting_date, month)
                else:
                    alternative_date = meeting_date
                
                meeting_info = {
                    'month': month,
                    'scheduled_date': meeting_date,
                    'actual_date': alternative_date,
                    'weekday_name': self.weekday_names[weekday],
                    'nth': nth,
                    'is_holiday': is_holiday,
                    'needs_shift': meeting_date != alternative_date
                }
                
                schedule.append(meeting_info)
        
        return schedule
    
    def _find_alternative_date(self, original_date, month):
        """代替日を探す"""
        alternative = original_date
        
        while (alternative.weekday() >= 5 or 
               alternative in self.holidays):
            alternative += timedelta(days=1)
            
            # 月を超えた場合は前の営業日を探す
            if alternative.month != month:
                alternative = original_date - timedelta(days=1)
                while (alternative.weekday() >= 5 or 
                       alternative in self.holidays):
                    alternative -= timedelta(days=1)
                break
        
        return alternative

# 使用例
scheduler = MeetingScheduler(holidays_2024)
meeting_schedule = scheduler.schedule_regular_meeting(2024, 1, 2)  # 第2火曜日

print("=== 2024年 月例会議スケジュール(第2火曜日)===")
for meeting in meeting_schedule:
    if meeting['needs_shift']:
        print(f"{meeting['month']:2d}月: {meeting['scheduled_date']} → {meeting['actual_date']} (振替)")
    else:
        print(f"{meeting['month']:2d}月: {meeting['actual_date']}")

給与支給日の計算

def calculate_salary_dates(year, pay_weekday=4, pay_nth=4):
    """給与支給日を計算(例:第4金曜日)"""
    salary_schedule = []
    
    for month in range(1, 13):
        pay_date = get_nth_weekday(year, month, pay_weekday, pay_nth)
        
        # 第4金曜日が存在しない場合は最終金曜日
        if pay_date is None:
            for n in range(5, 0, -1):
                pay_date = get_nth_weekday(year, month, pay_weekday, n)
                if pay_date:
                    break
        
        salary_info = {
            'month': month,
            'pay_date': pay_date,
            'is_standard': pay_date == get_nth_weekday(year, month, pay_weekday, pay_nth),
            'weekday': ['月', '火', '水', '木', '金', '土', '日'][pay_date.weekday()] if pay_date else None
        }
        
        salary_schedule.append(salary_info)
    
    return salary_schedule

# 使用例
salary_dates = calculate_salary_dates(2024, 4, 4)  # 第4金曜日
print("=== 2024年 給与支給予定日(第4金曜日基準)===")
for salary in salary_dates:
    standard_note = "" if salary['is_standard'] else " (最終金曜日)"
    print(f"{salary['month']:2d}月: {salary['pay_date']} ({salary['weekday']}){standard_note}")

イベントスケジューラー

class EventScheduler:
    def __init__(self):
        self.weekday_names = ['月', '火', '水', '木', '金', '土', '日']
    
    def create_event_series(self, year, events_config):
        """複数のイベントシリーズを作成
        
        events_config: [
            {'name': 'イベント名', 'weekday': 曜日, 'nth': 第何週, 'months': [月リスト]},
            ...
        ]
        """
        all_events = []
        
        for config in events_config:
            name = config['name']
            weekday = config['weekday']
            nth = config['nth']
            months = config.get('months', range(1, 13))
            
            for month in months:
                event_date = get_nth_weekday(year, month, weekday, nth)
                
                if event_date:
                    event_info = {
                        'name': name,
                        'date': event_date,
                        'month': month,
                        'weekday_name': self.weekday_names[weekday],
                        'nth': nth,
                        'config': config
                    }
                    all_events.append(event_info)
        
        # 日付順にソート
        all_events.sort(key=lambda x: x['date'])
        return all_events
    
    def find_conflicts(self, events):
        """イベントの日程重複をチェック"""
        conflicts = []
        
        for i, event1 in enumerate(events):
            for event2 in events[i+1:]:
                if event1['date'] == event2['date']:
                    conflicts.append({
                        'date': event1['date'],
                        'events': [event1['name'], event2['name']]
                    })
        
        return conflicts

# 使用例
events_config = [
    {
        'name': '月例会議',
        'weekday': 1,  # 火曜日
        'nth': 2,      # 第2週
        'months': range(1, 13)
    },
    {
        'name': '四半期レビュー',
        'weekday': 4,  # 金曜日
        'nth': 2,      # 第2週
        'months': [3, 6, 9, 12]
    },
    {
        'name': 'チームビルディング',
        'weekday': 4,  # 金曜日
        'nth': 1,      # 第1週
        'months': [2, 5, 8, 11]
    }
]

scheduler = EventScheduler()
events = scheduler.create_event_series(2024, events_config)
conflicts = scheduler.find_conflicts(events)

print("=== 2024年 イベントスケジュール ===")
current_month = 0
for event in events:
    if event['month'] != current_month:
        print(f"\n{event['month']}月:")
        current_month = event['month']
    print(f"  {event['date']} ({event['weekday_name']}): {event['name']}")

if conflicts:
    print("\n=== 日程重複 ===")
    for conflict in conflicts:
        print(f"{conflict['date']}: {' vs '.join(conflict['events'])}")

学校行事カレンダー

def create_school_calendar(year, start_month=4):
    """学校行事カレンダーを作成(4月始まり)"""
    school_events = []
    
    # 学期の定義
    semesters = {
        1: list(range(4, 8)),      # 1学期: 4-7月
        2: list(range(9, 13)),     # 2学期: 9-12月
        3: [1, 2, 3]               # 3学期: 1-3月
    }
    
    # 各学期の行事
    semester_events = {
        1: [
            {'name': '始業式', 'weekday': 0, 'nth': 1, 'month': 4},  # 4月第1月曜日
            {'name': '運動会', 'weekday': 5, 'nth': 2, 'month': 5},  # 5月第2土曜日
            {'name': '終業式', 'weekday': 4, 'nth': 3, 'month': 7},  # 7月第3金曜日
        ],
        2: [
            {'name': '始業式', 'weekday': 0, 'nth': 1, 'month': 9},  # 9月第1月曜日
            {'name': '文化祭', 'weekday': 6, 'nth': 2, 'month': 10}, # 10月第2日曜日
            {'name': '終業式', 'weekday': 4, 'nth': 3, 'month': 12}, # 12月第3金曜日
        ],
        3: [
            {'name': '始業式', 'weekday': 1, 'nth': 2, 'month': 1},  # 1月第2火曜日
            {'name': '卒業式', 'weekday': 4, 'nth': 3, 'month': 3},  # 3月第3金曜日
        ]
    }
    
    for semester, events in semester_events.items():
        for event_config in events:
            event_date = get_nth_weekday(
                year, 
                event_config['month'], 
                event_config['weekday'], 
                event_config['nth']
            )
            
            if event_date:
                school_events.append({
                    'name': event_config['name'],
                    'date': event_date,
                    'semester': semester,
                    'month': event_config['month']
                })
    
    # 日付順にソート
    school_events.sort(key=lambda x: x['date'])
    return school_events

# 使用例
school_calendar = create_school_calendar(2024)
print("=== 2024年度 学校行事カレンダー ===")

current_semester = 0
for event in school_calendar:
    if event['semester'] != current_semester:
        semester_name = ['', '1学期', '2学期', '3学期'][event['semester']]
        print(f"\n{semester_name}:")
        current_semester = event['semester']
    
    weekday_name = ['月', '火', '水', '木', '金', '土', '日'][event['date'].weekday()]
    print(f"  {event['date']} ({weekday_name}): {event['name']}")

統計分析機能

def analyze_nth_weekday_patterns(year, weekday, max_nth=5):
    """第N曜日の出現パターンを分析"""
    analysis = {
        'year': year,
        'weekday': weekday,
        'weekday_name': ['月', '火', '水', '木', '金', '土', '日'][weekday],
        'monthly_counts': {},
        'nth_occurrences': {n: 0 for n in range(1, max_nth + 1)},
        'total_occurrences': 0
    }
    
    for month in range(1, 13):
        month_count = 0
        for nth in range(1, max_nth + 1):
            nth_date = get_nth_weekday(year, month, weekday, nth)
            if nth_date:
                month_count += 1
                analysis['nth_occurrences'][nth] += 1
                analysis['total_occurrences'] += 1
        
        analysis['monthly_counts'][month] = month_count
    
    # 統計計算
    monthly_counts = list(analysis['monthly_counts'].values())
    analysis['statistics'] = {
        'avg_per_month': sum(monthly_counts) / 12,
        'min_per_month': min(monthly_counts),
        'max_per_month': max(monthly_counts),
        'months_with_5': sum(1 for count in monthly_counts if count == 5)
    }
    
    return analysis

# 使用例
friday_analysis = analyze_nth_weekday_patterns(2024, 4)  # 金曜日
print(f"=== 2024年 {friday_analysis['weekday_name']}曜日 出現パターン分析 ===")
print(f"年間総出現回数: {friday_analysis['total_occurrences']}回")
print(f"月平均: {friday_analysis['statistics']['avg_per_month']:.1f}回")
print(f"最少月: {friday_analysis['statistics']['min_per_month']}回")
print(f"最多月: {friday_analysis['statistics']['max_per_month']}回")
print(f"5回ある月: {friday_analysis['statistics']['months_with_5']}月")

print("\n第N金曜日の年間出現回数:")
for nth, count in friday_analysis['nth_occurrences'].items():
    print(f"  第{nth}金曜日: {count}回")

カスタム検索機能

def find_specific_date_patterns(year, criteria):
    """特定の条件に合う日付パターンを検索
    
    criteria例:
    {
        'weekdays': [0, 4],  # 月曜日と金曜日
        'nth_values': [1, 3],  # 第1と第3
        'months': [1, 2, 3],   # 1-3月
        'min_gap_days': 5      # 最小間隔5日
    }
    """
    matching_dates = []
    
    weekdays = criteria.get('weekdays', range(7))
    nth_values = criteria.get('nth_values', range(1, 6))
    months = criteria.get('months', range(1, 13))
    min_gap_days = criteria.get('min_gap_days', 0)
    
    for month in months:
        for weekday in weekdays:
            for nth in nth_values:
                target_date = get_nth_weekday(year, month, weekday, nth)
                
                if target_date:
                    # 最小間隔チェック
                    if min_gap_days > 0 and matching_dates:
                        last_date = matching_dates[-1]['date']
                        gap = (target_date - last_date).days
                        if gap < min_gap_days:
                            continue
                    
                    weekday_name = ['月', '火', '水', '木', '金', '土', '日'][weekday]
                    matching_dates.append({
                        'date': target_date,
                        'month': month,
                        'weekday': weekday,
                        'weekday_name': weekday_name,
                        'nth': nth
                    })
    
    return matching_dates

# 使用例
search_criteria = {
    'weekdays': [0, 4],    # 月曜日と金曜日
    'nth_values': [1, 3],  # 第1と第3
    'months': [1, 2, 3, 4, 5, 6],  # 上半期
    'min_gap_days': 7      # 最低1週間の間隔
}

search_results = find_specific_date_patterns(2024, search_criteria)
print("=== カスタム検索結果 ===")
print("条件: 上半期の第1・第3月曜日・金曜日(最低1週間間隔)")
for result in search_results:
    print(f"{result['date']} ({result['weekday_name']}) - 第{result['nth']}{result['weekday_name']}曜日")

まとめ

  • 基本機能:月の1日から計算して第N曜日を特定
  • 存在チェック:月をまたがないかの検証が重要
  • 祝日対応:振替日の自動計算機能
  • 実用例:会議スケジュール、給与日、学校行事
  • 分析機能:出現パターンの統計分析
  • 検索機能:複雑な条件での日付パターン検索

これらの実装により、様々なビジネスシーンでの定期的な日程管理が効率的に行えます。

■プロンプトだけでオリジナルアプリを開発・公開してみた!!

■AI時代の第一歩!「AI駆動開発コース」はじめました!

テックジム東京本校で先行開始。

■テックジム東京本校

「武田塾」のプログラミング版といえば「テックジム」。
講義動画なし、教科書なし。「進捗管理とコーチング」で効率学習。
より早く、より安く、しかも対面型のプログラミングスクールです。

<短期講習>5日で5万円の「Pythonミニキャンプ」開催中。

<月1開催>放送作家による映像ディレクター養成講座

<オンライン無料>ゼロから始めるPython爆速講座