祝日を返すマクロ [Excel VBA]

2024/03/26 05:56 OS別::Windowsその他::技術情報
Excel VBA で、毎年祝祭日リストを更新しなくても祝日を自動計算して返すマクロができないか見ていたら、なんとできそうでした。

使うためには、fnWeekday 関数(Public 関数)を Excel のセルに記述して利用してください。
少なくとも、2023年~2025年は問題なさそうです。
(2023.6.25 2025年の「敬老の日」が祝日にならない問題を修正)
(2024.3.2 Date 関数の戻り値が日付型と判定できない問題を修正)

最終的に挙動を確認した Excel バージョンは、次の通りです。
Microsoft® Excel® for Microsoft 365 MSO (バージョン 2401 ビルド 16.0.17231.20236) 64 ビット

仕様

モジュール

モジュール内の関数を Excel のセルで利用します。
主に利用する関数は fnWeekday です。

セルには =fnWeekday(シリアル値) を指定することで戻り値を得ることができます。

fnWeekday 実行サンプル
fnWeekday 実行サンプル

また、=fnWeekday(シリアル値, 1) のように第2引数に 1 を指定すると、平日と休日、祝日の名称のいずれかを得ることができます。
これが 2 の場合は「祝日の趣旨」(画像における D 列)を得ます。

fnWeekday オプションを指定時の実行サンプル
fnWeekday オプションを指定時の実行サンプル

出力利用例
セル内の記述例出力例備考
=fnWeekday(DATE(2024,5,5))8祝日を示す 8 を表示する
=fnWeekday(DATE(2024,5,5),1)こどもの日平日/休日/(祝日の名称)の、いずれかを表示する
=fnWeekday(DATE(2024,5,5),2)こどもの人格を重んじ、こどもの幸福をはかるとともに、母に感謝する。祝日の趣旨を表示する
fnWeekday 以外の関数は、内部で細かい計算をするために使われます。
(もちろん、セル内で利用することも可能です)

戻り値

戻り値は、第2引数の指定で変化します。
上述したように、デフォルトで weekday 関数とほぼ同じ*1ですが、追加で 8=祝日としています。

以下、第2引数を明記した場合の挙動を記載します。
0の場合:数値(デフォルト)
第2引数に 0 を指定した(もしくは指定しない)場合に出力される番号です。
デフォルト
番号内部変数意味備考
1vbSunday日曜日
2vbMonday月曜日
3vbTuesday火曜日
4vbWednesday水曜日
5vbThursday木曜日
6vbFriday金曜日
7vbSaturday土曜日
8-祝日
1 の場合:名称
第2引数に 1 を指定した場合に出力される文字列です。
名称を返す
文字列意味備考
休日土日どちらか
平日土日祝以外の平日(実際は月~金曜日)
(各祝日名)祝日
祝日の名称は、国民の祝日に関する法律に準拠しています。
2 の場合:趣旨
第2引数に 2 を指定した場合に出力される文字列です。
趣旨を返す
趣旨意味備考
(Nothing)平日/休日であり、祝日ではない日
(各祝日の趣旨)法律などで定義される趣旨
(Nothing) については、表示しないように(Null 表示に)するかもしれません。

*1 : weekday 関数のデフォルトの戻り値は 1=日, 2=月, 3=火, 4=水, 5=木, 6=金, 7=土になっています。

関数

作成したモジュールは、つぎの五つの関数から成り立っています。

fnWeekday(Serial, [Switch])

このモジュールの本丸で、公式の weekday 関数のように使います。
戻り値として曜日(vbMonday から vbSaturday)だけでなく、追加で Holiday を意味する 8 も返します。

オプションには、シリアル値が必要です。
戻り値の内容を変えるために、Switch 番号も指定可能です。

第2引数を指定した際に、セルに表示される内容は、次の通りです。
Switch意味備考
0曜日と祝日を意味する 1~8 の数字を返すSwitch を省略した場合のデフォルト
1平日/休日/祝日の名称を返す
2祝日の趣旨を返す

fnDate(Year, Month, Day)

Excel 関数 date コマンドを模したものです。
オプションには、年・月・日の、三つが必要です。

Excel VBA の場合、DateValue コマンドを使えばいいのですが、年月日を文字列で指定しなければならないため、コードがかなり読みにくくなっていました。
可読性を目的に関数化しています。

fnHappyMonday(Year, Month, Weeks)

ハッピーマンデー制度でシリアル値を取得するのに必要な、x年x月の第x月曜日、という指定でシリアル値を取得するための関数です。
オプションには、年・月・第x週の、三つが必要です。

考え方としては、月の1日が何曜日なのかを見て、そこから月曜日が何日なのかを割り出します。
あとは、7日に第x週のx-1 を掛けたものを足してあげれば、シリアル値を取得できます。

fnVernalEquinox(Year)

春分の日を割り出し、シリアル値を取得するための関数です。
オプションには、年の指定が必要です。

1行しかない、かなり短いコードですが関数化しています。
計算で出力した日付ではない場合、ここで出力情報を調整する予定です。

fnAutumnalEquinox(Year)

秋分の日を割り出し、シリアル値を取得するための関数です。
オプションには、年の指定が必要です。

1行しかない、かなり短いコードですが関数化しています。
計算で出力した日付ではない場合、ここで出力情報を調整する予定です。

ソースコード

Option Explicit

Public Function fnWeekday(Serial As Date, Optional Switch As Byte = 0)
' #########################################################################
' #
' # 呼び出されると、引数の Serial から曜日/祝日情報を返すマクロ
' # デフォルトの戻り値は、以下の通り
' #
' # 戻り値の定義
' #  0 = 平日(月曜~金曜)
' #  1 = 日曜日(vbSunday)
' #  2 = 曜日月(vbMonday)
' #  3 = 火曜日(vbTuesday)
' #  4 = 水曜日(vbWednesday)
' #  5 = 木曜日(vbThursday)
' #  6 = 金曜日(vbFriday)
' #  7 = 土曜日(vbSaturday)
' #  8 = 祝日
' #
' #########################################################################

' ### 変数定義 #########################

Dim DEFpDayName     As String
Dim DEFpDayMeaning  As String
Dim pDayName        As String
Dim pDayMeaning     As String
Dim pRV             As Byte
Dim pYYYY           As Integer
Dim fSubstitute     As Boolean

' ### 変数設定 #########################

DEFpDayName = "平日"
DEFpDayMeaning = "(Nothing)"

pDayName = DEFpDayName
pDayMeaning = DEFpDayMeaning

pYYYY = Year(Serial)

' ### メイン処理 #######################

' serial に指定された年月日と、該当する年の祝日に相当するシリアル値を比較する。
' 該当の祝日であれば、第2引数通りに変数を設定する。
' もし該当の祝日名があれば、戻り値 8 を指定する。

Select Case Serial
    ' 日付が固定されているもの
    Case fnDate(pYYYY, 1, 1)
        pDayName = "元旦"
        pDayMeaning = "年のはじめを祝う。"
    Case fnDate(pYYYY, 1, 15)
        If pYYYY <= 1999 Then
            pDayName = "成人の日"
            pDayMeaning = "おとなになったことを自覚し、みずから生き抜こうとする青年を祝い、励ます。"
        End If
    Case fnDate(pYYYY, 2, 11)
        pDayName = "建国記念の日"
        pDayMeaning = "建国をしのび、国を愛する心を養う。"
    Case fnDate(pYYYY, 2, 23)
        If 2019 < pYYYY Then
            pDayName = "天皇誕生日"
            pDayMeaning = "天皇の誕生日を祝う。"
        End If
    Case fnDate(pYYYY, 4, 29)
        If 2007 <= pYYYY Then
            pDayName = "昭和の日"
            pDayMeaning = "激動の日々を経て、復興を遂げた昭和の時代を顧み、国の将来に思いをいたす。"
        ElseIf 1989 <= pYYYY Then
            pDayName = "みどりの日"
            pDayMeaning = "自然の恩恵に感謝する"
        ElseIf 1948 <= pYYYY Then
            pDayName = "天皇誕生日"
            pDayMeaning = "天皇の誕生を祝う(昭和天皇)"
        ElseIf 1927 <= pYYYY Then
            pDayName = "天長節"
            pDayMeaning = "天地が永久であるように天皇の治世も続くように"
        End If
    Case fnDate(pYYYY, 5, 3)
        pDayName = "憲法記念日"
        pDayMeaning = "日本国憲法の施行を記念し、国の成長を期する。"
    Case fnDate(pYYYY, 5, 4)
        If 2007 <= pYYYY Then
            pDayName = "みどりの日"
            pDayMeaning = "自然に親しむとともにその恩恵に感謝し、豊かな心をはぐくむ。"
        End If
    Case fnDate(pYYYY, 5, 5)
        pDayName = "こどもの日"
        pDayMeaning = "こどもの人格を重んじ、こどもの幸福をはかるとともに、母に感謝する。"
    Case fnDate(pYYYY, 7, 20)
        If pYYYY <= 2002 Then
            pDayName = "海の日"
            pDayMeaning = "海の恩恵に感謝するとともに、海洋国日本の繁栄を願う。"
        End If
    Case fnDate(pYYYY, 8, 11)
        ' 2014年制定、2016年より施工
        If 2016 <= pYYYY Then
            pDayName = "山の日"
            pDayMeaning = "山に親しむ機会を得て、山の恩恵に感謝する。"
        End If
    Case fnDate(pYYYY, 9, 15)
        If pYYYY <= 2002 Then
            pDayName = "敬老の日"
            pDayMeaning = "多年にわたり社会につくしてきた老人を敬愛し、長寿を祝う。"
        End If
    Case fnDate(pYYYY, 11, 3)
        pDayName = "文化の日"
        pDayMeaning = "自由と平和を愛し、文化をすすめる。"
    Case fnDate(pYYYY, 11, 23)
        pDayName = "勤労感謝の日"
        pDayMeaning = "勤労をたっとび、生産を祝い、国民たがいに感謝しあう。"
    Case fnDate(pYYYY, 12, 23)
        If pYYYY < 2019 Then
            ' 以降、上皇陛下
            pDayName = "天皇誕生日"
            pDayMeaning = "天皇の誕生日を祝う。"
        End If
End Select

Select Case Serial
    ' ハッピーマンデー
    Case fnHappyMonday(pYYYY, 1, 2)
        ' 2000 年より適用
        If 2000 <= pYYYY Then
            pDayName = "成人の日"
            pDayMeaning = "おとなになったことを自覚し、みずから生き抜こうとする青年を祝いはげます。"
        End If
    Case fnHappyMonday(pYYYY, 7, 3)
        If 2003 <= pYYYY Then
            ' 2003 年より適用
            pDayName = "海の日"
            pDayMeaning = "海の恩恵に感謝するとともに、海洋国日本の繁栄を願う。"
        End If
    Case fnHappyMonday(pYYYY, 9, 3)
        If 2003 <= pYYYY Then
            pDayName = "敬老の日"
            pDayMeaning = "多年にわたり社会につくしてきた老人を敬愛し、長寿を祝う。"
        End If
    Case fnHappyMonday(pYYYY, 10, 2)
        ' 2000 年より適用。2020 年より名称変更
        If 2020 <= pYYYY Then
            pDayName = "スポーツの日"
            pDayMeaning = "スポーツを楽しみ、他者を尊重する精神を培うとともに、健康で活力ある社会の実現を願う。"
        ElseIf 2000 <= pYYYY Then
            pDayName = "体育の日"
            pDayMeaning = "スポーツにしたしみ、健康な心身をつちかう日"
        End If

    ' 春分/秋分の日
    Case fnVernalEquinox(pYYYY)
        pDayName = "春分の日"
        pDayMeaning = "自然をたたえ、生物をいつくしむ。"
    Case fnAutumnalEquinox(pYYYY)
        pDayName = "秋分の日"
        pDayMeaning = "祖先をうやまい、なくなった人々をしのぶ。"
End Select

' 天皇の即位の日及び即位礼正殿の儀の行われる日を休日とする法律
Select Case Serial
    Case fnDate(2019, 5, 1)
        pDayName = "天皇の即位の日"
    Case fnDate(2019, 5, 2)
        pDayName = "国民の休日"
    Case fnDate(2019, 10, 22)
        pDayName = "即位礼正殿の儀の行われる日"
End Select

' 振替休日
Select Case Serial
    Case fnDate(pYYYY, 1, 1) + 1
        If Weekday(Serial) = vbMonday Then fSubstitute = True
    Case fnDate(pYYYY, 2, 11) + 1
        If Weekday(Serial) = vbMonday Then fSubstitute = True
    Case fnDate(pYYYY, 2, 23) + 1
        If 2019 < pYYYY Then
            If Weekday(Serial) = vbMonday Then fSubstitute = True
        End If
    Case fnDate(pYYYY, 4, 29) + 1
        If Weekday(Serial) = vbMonday Then fSubstitute = True
    Case fnDate(pYYYY, 5, 5) + 1
        ' 5月5日が日曜~火曜日だったら、5/6 が振替休日
        If vbSunday <= Weekday(fnDate(pYYYY, 5, 5)) And Weekday(fnDate(pYYYY, 5, 5)) <= vbTuesday Then
            fSubstitute = True
        End If
    Case fnDate(pYYYY, 8, 11) + 1
        If Weekday(Serial) = vbMonday Then fSubstitute = True
    Case fnDate(pYYYY, 11, 3) + 1
        If Weekday(Serial) = vbMonday Then fSubstitute = True
    Case fnDate(pYYYY, 11, 23) + 1
        If Weekday(Serial) = vbMonday Then fSubstitute = True
    Case fnDate(pYYYY, 12, 23) + 1
        If pYYYY < 2019 Then
            ' 以降、上皇陛下
            If Weekday(Serial) = vbMonday Then fSubstitute = True
        End If
        
    ' ハッピーマンデー
    Case fnHappyMonday(pYYYY, 1, 2) + 1
        If Weekday(Serial) = vbMonday Then fSubstitute = True
    Case fnHappyMonday(pYYYY, 7, 3) + 1
        If Weekday(Serial) = vbMonday Then fSubstitute = True
    Case fnHappyMonday(pYYYY, 9, 3) + 1
        ' シルバーウィーク対応
        ' もし、敬老の日の2日後が秋分の日なら、国民の休日にする。
        If fnHappyMonday(pYYYY, 9, 3) + 2 = fnAutumnalEquinox(pYYYY) Then
            pDayName = "国民の休日"
        ElseIf Weekday(Serial) = vbMonday Then
            fSubstitute = True
        End If
        
    Case fnHappyMonday(pYYYY, 10, 2) + 1
        If Weekday(Serial) = vbMonday Then fSubstitute = True

    ' 春分/秋分の日
    Case fnVernalEquinox(pYYYY) + 1
        If Weekday(Serial) = vbMonday Then fSubstitute = True
    Case fnAutumnalEquinox(pYYYY) + 1
        If Weekday(Serial) = vbMonday Then fSubstitute = True
    'Case DateValue("2020/9/22")
    '    DayName = "秋分の日"

End Select

If fSubstitute = True Then pDayName = "振替休日"

' -----------------------------------------------------------
' 令和 2 年(2020年)及び令和 3 年(2021年)に限り、
' 東京オリンピック・パラリンピック競技大会の開催にあわせ、
' 「海の日」、「スポーツの日」及び「山の日」が移動している。
' -----------------------------------------------------------

' 令和2年 (2020) & 令和3年 (2021)
Select Case Serial
    ' 海の日
    Case fnHappyMonday(2020, 7, 3), fnHappyMonday(2021, 7, 3)
        pDayName = DEFpDayName
        pDayMeaning = DEFpDayMeaning
    Case fnDate(2020, 7, 23)
        pDayName = "海の日"
        pDayMeaning = "海の恩恵に感謝するとともに、海洋国日本の繁栄を願う。"
    Case fnDate(2021, 7, 22)
        pDayName = "海の日"
        pDayMeaning = "海の恩恵に感謝するとともに、海洋国日本の繁栄を願う。"

    ' スポーツの日
    Case fnHappyMonday(2020, 10, 2), fnHappyMonday(2021, 10, 2)
        pDayName = DEFpDayName
        pDayMeaning = DEFpDayMeaning
    Case fnDate(2020, 7, 24)
        pDayName = "スポーツの日"
        pDayMeaning = "スポーツを楽しみ、他者を尊重する精神を培うとともに、健康で活力ある社会の実現を願う。"
    Case fnDate(2021, 7, 23)
        pDayName = "スポーツの日"
        pDayMeaning = "スポーツを楽しみ、他者を尊重する精神を培うとともに、健康で活力ある社会の実現を願う。"

    ' 山の日
    Case fnDate(2020, 8, 11), fnDate(2021, 8, 11)
        pDayName = DEFpDayName
        pDayMeaning = DEFpDayMeaning
    Case fnDate(2020, 8, 10)
        pDayName = "山の日"
        pDayMeaning = "山に親しむ機会を得て、山の恩恵に感謝する。"
    Case fnDate(2021, 8, 8)
        pDayName = "山の日"
        pDayMeaning = "山に親しむ機会を得て、山の恩恵に感謝する。"
End Select

' 祝日値判定
If pDayName = DEFpDayName Then
    ' 平日/休日であれば、戻り値を 1~7 で指定する。
    ' ## 曜日選択 #####
    If IsDate(Serial) Then
        Select Case Weekday(Serial)
            Case vbSunday
                pRV = vbSunday
                pDayName = "休日"
            Case vbMonday
                pRV = vbMonday
            Case vbTuesday
                pRV = vbTuesday
            Case vbWednesday
                pRV = vbWednesday
            Case vbThursday
                pRV = vbThursday
            Case vbFriday
                pRV = vbFriday
            Case vbSaturday
                pRV = vbSaturday
                pDayName = "休日"
        End Select
    Else
        ' IsDate の判定内容にエラーがある場合
        fnWeekday = "祝日値判定エラー"
        Exit Function
    End If
Else
    ' 平日でなければ、戻り値を 8 で指定する。
    pRV = 8
End If

' ### 終了処理 #########################

' オプションの内容で、戻り値を変更する。
Select Case Switch
    Case 0
        fnWeekday = pRV
    Case 1
        fnWeekday = pDayName
    Case 2
        fnWeekday = pDayMeaning
End Select

' ######################################

End Function

Public Function fnDate(Year, Month, Day)
' #########################################################################
' #
' # VBA の date 関数は、Excel sheet の date 関数と異なり、文字列を指定する。
' # この関数は、セルで利用する date 関数と同じ使い勝手を実現するもの。
' #
' #########################################################################

fnDate = DateValue(Year & "/" & Month & "/" & Day)

End Function

Public Function fnHappyMonday(Year, Month, Weeks)
' #########################################################################
' #
' # ハッピーマンデー用のシリアル値を返す。
' # 指定された、Year 年 Month 月の第 Weeks 週の月曜日(シリアル値)を返す。
' #
' # 参考資料
' # http://www.relief.jp/itnote/archives/003241.php
' #########################################################################

Dim pMondayDate     As Byte
Dim pFirstMonDay    As Date

' ## 第1週目の月曜日を求める。

' 先に指定月の1日が何曜日なのかを見て、次の月曜日が何日なのかを指定する。
Select Case Weekday(fnDate(Year, Month, 1))
    Case vbSunday       ' 1
        ' x月1日が日曜日なら、月曜日は翌日の 2 日
        pMondayDate = 2
    Case vbMonday       ' 2
        ' x月1日が月曜日なので、そのまま 1 日
        pMondayDate = 1
    Case vbTuesday      ' 3
        ' x月1日が火曜日なので、月曜日は 6 日後の 7 日
        pMondayDate = 7
    Case vbWednesday    ' 4
        ' x月1日が水曜日なので、月曜日は 5 日後の 6 日
        pMondayDate = 6
    Case vbThursday     ' 5
        pMondayDate = 5
    Case vbFriday       ' 6
        pMondayDate = 4
    Case vbSaturday     ' 7
        pMondayDate = 3
End Select

' 第1週目の月曜日から何週間後なのか、必要な日数を足してシリアル値を返す。
fnHappyMonday = fnDate(Year, Month, pMondayDate) + (7 * (Weeks - 1))

End Function

Public Function fnVernalEquinox(Year)
' #########################################################################
' #
' # 呼び出されると、引数の Year から算出した 春分の日 情報を返すマクロ
' #
' # 参考 URL http://www.wanichan.com/pc/excel/2010/5/page07.html
' #########################################################################
fnVernalEquinox = fnDate(Year, 3, Int(20.8431 + 0.242194 * (Year - 1980) - Int((Year - 1980) / 4)))

End Function

Public Function fnAutumnalEquinox(Year)
' #########################################################################
' #
' # 呼び出されると、引数の Year から算出した 秋分の日 情報を返すマクロ
' #
' # 参考 URL http://www.wanichan.com/pc/excel/2010/5/page07.html
' #########################################################################
fnAutumnalEquinox = fnDate(Year, 9, Int(23.2488 + 0.242194 * (Year - 1980) - Int((Year - 1980) / 4)))

End Function

その他

zip ファイル

コピペより、インポート可能なファイルが良いという方向け。
現在バージョン
過去バージョン

更新履歴

  • 2023.3.5 細かい表現と、マクロの微修正を行った。
  • 2023.6.25 敬老の日に関するご指摘をいただき、ハッピーマンデーの扱いを修正*2 した。
  • 2024.3.2 オプションに Date を指定した場合 0 が返ってくる現象についてご指摘をいただき、IsDate 関数で判断する値を CVDate 関数で日付型へ事前に変換*3するようにした。
  • 2024.3.2 IsDate 関数で判断する Serial について、fnWeekday 関数にオプションとして入力する時点で Date 型であることを強制した。

*2 : シリアル値で祝日を判断するフローで、固定されていた敬老の日(2002年以前)を分岐させたあとにハッピーマンデーの分岐が来ており、ハッピーマンデー対象である敬老の日が分岐済みの扱いになってチェックされない流れになっていた。

*3 : VBA の IsDate 関数が、Date 関数で取得した値そのままだと、数値を日付と判断できない。

参考サイト様