Excel VBA で祝日一覧を出力

まだちょっと不便なところがある……というか、貼り付けてそのまま関数書けば実行できるということろまではいけなかったんだけども。
一応、ActiveX のボタン(フォームコントロールのボタンでは呼び出せなかった)もしくは、メニューに項目を追加して、そこで関数名で呼び出せばなんとかリスト出力まではできるようになったので、残しておく。

2019年、2020年の、なんとイレギュラーなことか。

いちおう、2030年までは、他のサイトの情報と照らし合わせて正確に出力していることを確認済み。
この1~2年のようなイレギュラーがなければ、問題なく動いてくれるだろう。

これで、この関数は
・休日・祝日判定
・第x週目の月曜日を出力
・祝日リスト出力

という、なんとも気の抜けそうな機能を充実させることに成功した!

Option Explicit

' セル位置関係
Dim aRow            As Long
Dim aCol            As Long
Dim wi              As Long

Function getHolidayList()
' #########################################################################
' # 西暦を入れるとアクティブセルから下に祝日一覧を入れるマクロ
' # セルに関数名を入れた使い方はできないので、改造中。
' # ActiveX コントロールのボタン、もしくはメニューバーから呼び出すこと。
' #########################################################################

Dim msgInputYear    As String
Dim msgOverRideChk  As String

Dim pInputYear      As Variant

Dim chk             As Variant
Dim tmp             As Variant

msgInputYear = "出力したい西暦を入力してください。"
msgOverRideChk = "現在のアクティブセルから下に2列、リストを出力します。" & vbCrLf & "(既存の情報は上書き)"

wi = 1

pInputYear = InputBox(msgInputYear, "西暦", Year(Now))
Select Case VarType(pInputYear)
    ' Empty値 (0)
    Case vbEmpty
        Exit Function
    
    ' 文字列型 (8)
    Case vbString
        pInputYear = StrConv(pInputYear, vbNarrow)
    
    Case Else
        MsgBox "Error"

End Select

If IsNumeric(pInputYear) = False Then
    'MsgBox "数字を入力してください", vbOKOnly, "Error"
    Exit Function
End If

If MsgBox(msgOverRideChk, vbYesNo) Then
    ' アクティブセルの位置を取得
    aRow = ActiveCell.Row
    aCol = ActiveCell.Column

    Cells(aRow, aCol) = pInputYear & "年"
    Cells(aRow, aCol + 1) = "祝日"
    
    '元日 1月1日
    tmp = DateValue(pInputYear & "/1/1")
    chk = wDD(tmp, "元日")
    If Weekday(tmp + 1) = vbMonday Then chk = wDD(tmp + 1, "振替休日")
    
    '成人の日 1月の第2月曜日
    chk = wDD(NumWeek(pInputYear, 1, 2), "成人の日")
    
    '建国記念の日 政令で定める日(2/11)
    tmp = DateValue(pInputYear & "/2/11")
    chk = wDD(tmp, "建国記念の日")
    If Weekday(tmp + 1) = vbMonday Then chk = wDD(tmp + 1, "振替休日")
    
    '天皇誕生日 12月23日 → 2月23日(2019年 天皇の即位に伴う変更)
    If 2019 < pInputYear Then
        chk = wDD(DateValue(pInputYear & "/2/23"), "天皇誕生日")
        If Weekday(DateValue(pInputYear & "/2/24")) = vbMonday Then chk = wDD(DateValue(pInputYear & "/2/24"), "振替休日")
    End If
    
    '春分の日
    tmp = DateValue(pInputYear & "/3/" & Int(20.8431 + 0.242194 * (pInputYear - 1980) - Int((pInputYear - 1980) / 4)))
    chk = wDD(tmp, "春分の日")
    If Weekday(tmp + 1) = vbMonday Then chk = wDD(tmp + 1, "振替休日")

    '昭和の日 4月29日
    tmp = DateValue(pInputYear & "/4/29")
    chk = wDD(tmp, "昭和の日")
    If Weekday(tmp + 1) = vbMonday Then chk = wDD(tmp + 1, "振替休日")
    
    '天皇の即位の日 2019年5月1日
    If pInputYear = 2019 Then
        chk = wDD(DateValue("2019/4/30"), "国民の休日")
        chk = wDD(DateValue("2019/5/1"), "天皇の即位の日")
        chk = wDD(DateValue("2019/5/2"), "国民の休日")
    End If

    '憲法記念日 5月3日
    chk = wDD(DateValue(pInputYear & "/5/3"), "憲法記念日")
    
    'みどりの日 5月4日
    chk = wDD(DateValue(pInputYear & "/5/4"), "みどりの日")
    
    'こどもの日 5月5日
    tmp = DateValue(pInputYear & "/5/5")
    chk = wDD(tmp, "こどもの日")
    
        ' 5月5日が日曜~火曜日だったら、振替休日確定
        If vbSunday <= Weekday(tmp) And Weekday(tmp) <= vbTuesday Then
            chk = wDD(tmp + 1, "振替休日")
        End If

    '海の日 7月の第3月曜日
    If pInputYear = 2020 Then
        chk = wDD(DateValue("2020/7/23"), "海の日")
        chk = wDD(DateValue("2020/7/24"), "スポーツの日")
    Else
        tmp = NumWeek(pInputYear, 7, 3)
        chk = wDD(tmp, "海の日")
        If Weekday(tmp + 1) = vbMonday Then chk = wDD(tmp + 1, "振替休日")
    End If
    
    '山の日 8月11日
    If pInputYear = 2020 Then
        chk = wDD(DateValue("2020/8/10"), "山の日")
    Else
        tmp = DateValue(pInputYear & "/8/11")
        chk = wDD(tmp, "山の日")
        If Weekday(tmp + 1) = vbMonday Then chk = wDD(tmp + 1, "振替休日")
    End If

    '敬老の日 9月の第3月曜日
    chk = wDD(NumWeek(pInputYear, 9, 3), "敬老の日")
    
    '国民の休日 祝日に挟まれた平日
    tmp = DateValue(pInputYear & "/9/" & Int(23.2488 + 0.242194 * (pInputYear - 1980) - Int((pInputYear - 1980) / 4)))
    'If Weekday(tmp) = vbWednesday Then chk = wDD(tmp - 1, "国民の休日")
    If tmp - 2 = NumWeek(pInputYear, 9, 3) Then chk = wDD(tmp - 1, "国民の休日")
    
    '秋分の日
    'tmp = DateValue(pInputYear & "/9/" & Int(23.2488 + 0.242194 * (pInputYear - 1980) - Int((pInputYear - 1980) / 4)))
    chk = wDD(DateValue(tmp), "秋分の日")
    If Weekday(tmp + 1) = vbMonday Then chk = wDD(tmp + 1, "振替休日")

    '体育の日 10月の第2月曜日 (2020年からスポーツの日に名称変更)
    If pInputYear < 2020 Then
        chk = wDD(NumWeek(pInputYear, 10, 2), "体育の日")
    Else
        If pInputYear <> 2020 Then chk = wDD(NumWeek(pInputYear, 10, 2), "スポーツの日")
    End If

    '即位礼正殿の儀の行われる日 2019年10月22日
    If pInputYear = 2019 Then chk = wDD(DateValue("2019/10/22"), "即位礼正殿の儀の行われる日")

    '文化の日 11月3日
    tmp = DateValue(pInputYear & "/11/3")
    chk = wDD(tmp, "文化の日")
    If Weekday(tmp + 1) = vbMonday Then chk = wDD(tmp + 1, "振替休日")
    
    '勤労感謝の日 11月23日
    tmp = DateValue(pInputYear & "/11/23")
    chk = wDD(tmp, "勤労感謝の日")
    If Weekday(tmp + 1) = vbMonday Then chk = wDD(tmp + 1, "振替休日")
    
    '天皇誕生日 12月23日 → 2月23日(2019年 天皇の即位に伴う変更)
    If pInputYear < 2019 Then
        tmp = DateValue(pInputYear & "/12/23")
        chk = wDD(tmp, "天皇誕生日")
        If Weekday(tmp + 1) = vbMonday Then chk = wDD(tmp + 1, "振替休日")
    End If

End If


End Function

Public Function wDD(Serial, HolidayName)

' Write Date Data
Cells(aRow + wi, aCol) = Serial
Cells(aRow + wi, aCol + 1) = HolidayName

wi = wi + 1

End Function

Public Function chkWorkDay(Optional Serial As Date)
' #########################################################################
' # 呼び出されると、翌営業日を返すマクロ
' # 2020/3/11 初版作成
' #########################################################################

' ### 変数 ################################################################

Dim chkDate As Date
Dim fDate   As Integer

If Serial > 0 Then
    chkDate = Serial
Else
    chkDate = Date
End If

fDate = 1

' ### メイン ##############################################################

Do While fDate > 0
    chkDate = chkDate + 1
    fDate = chkHoliday(chkDate)
Loop

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

chkWorkDay = chkDate

End Function


Public Function chkHoliday(Serial, Optional RVFlag As Byte = 0)
' #########################################################################
' # 呼び出されると、引数のSerialから休日/祝日判定フラグを返すマクロ
' # 更新履歴
' # 2019/12/5 2020年の特殊な祝日情報に対応した。
' # 2020/3/11 土日を休日と返すようにした。
' #########################################################################

' 戻り値の定義
' 0 = 平日
' 1 = 日曜日(vbSunday)
' 7 = 土曜日(vbSaturday)
' 8 = 祝日

' 変数の宣言
Dim RV As Byte
Dim DayName As String
Dim YYYY As Integer
Dim fSubstitute As Boolean ' 振替休日フラグ

' ### メイン ##############################################################

DayName = "平日"
YYYY = Year(Serial)

' ## 祝日判定 #####
'元日 1月1日
'成人の日 1月の第2月曜日
'建国記念の日 政令で定める日(2/11)
'春分の日 春分の日
'昭和の日 4月29日
'憲法記念日 5月3日
'みどりの日 5月4日
'こどもの日 5月5日
'海の日 7月の第3月曜日
'山の日 8月11日
'敬老の日 9月の第3月曜日
'秋分の日 秋分の日
'体育の日 10月の第2月曜日 (2020年からスポーツの日に名称変更)
'文化の日 11月3日
'勤労感謝の日 11月23日
'天皇誕生日 12月23日 → 2月23日(2019年 天皇の即位に伴う変更)
'国民の休日 祝日に挟まれた平日
'天皇の即位の日及び即位礼正殿の儀の行われる日を休日とする法律 2019年5月1日と2019年10月22日


' 規定の日付
Select Case Serial
  Case DateValue(YYYY & "/1/1")
    DayName = "元旦"
  Case DateValue(YYYY & "/1/2")
    DayName = "年始"
  Case DateValue(YYYY & "/1/3")
    DayName = "年始"
  Case NumWeek(YYYY, 1, 2)
    DayName = "成人の日"
  Case DateValue(YYYY & "/2/11")
    DayName = "建国記念の日"
  Case DateValue(YYYY & "/2/23")
    If 2019 < YYYY Then
      DayName = "天皇誕生日"
    End If
  Case DateValue(YYYY & "/3/" & Int(20.8431 + 0.242194 * (YYYY - 1980) - Int((YYYY - 1980) / 4)))
    ' 参考URL http://www.wanichan.com/pc/excel/2010/5/page07.html
    DayName = "春分の日"
  Case DateValue(YYYY & "/4/29")
    DayName = "昭和の日"
  Case DateValue("2019/4/30")
    DayName = "国民の休日"
  Case DateValue("2019/5/1")
    DayName = "天皇の即位の日"
  Case DateValue("2019/5/2")
    DayName = "国民の休日"
  Case DateValue(YYYY & "/5/3")
    DayName = "憲法記念日"
  Case DateValue(YYYY & "/5/4")
    DayName = "みどりの日"
  Case DateValue(YYYY & "/5/5")
    DayName = "こどもの日"
  Case NumWeek(YYYY, 7, 3)
    DayName = "海の日"
  Case DateValue(YYYY & "/8/11")
    DayName = "山の日"
  Case NumWeek(YYYY, 9, 3)
    DayName = "敬老の日"
  Case NumWeek(YYYY, 9, 3) + 1
    If DateValue(YYYY & "/9/" & Int(23.2488 + 0.242194 * (YYYY - 1980) - Int((YYYY - 1980) / 4))) = NumWeek(YYYY, 9, 3) + 2 Then
      DayName = "国民の休日"
    End If
  Case DateValue(YYYY & "/9/" & Int(23.2488 + 0.242194 * (YYYY - 1980) - Int((YYYY - 1980) / 4)))
    ' 参考URL http://www.wanichan.com/pc/excel/2010/5/page07.html
    DayName = "秋分の日"
  Case NumWeek(YYYY, 10, 2)
    If YYYY <= 2019 Then
      DayName = "体育の日"
    Else
      DayName = "スポーツの日"
    End If
  Case DateValue("2019/10/22")
    DayName = "即位礼正殿の儀の行われる日"
  Case DateValue(YYYY & "/11/3")
    DayName = "文化の日"
  Case DateValue(YYYY & "/11/23")
    DayName = "勤労感謝の日"
  Case DateValue(YYYY & "/12/23")
    If YYYY < 2019 Then
      DayName = "天皇誕生日"
    End If
  Case DateValue(YYYY & "/12/31")
    DayName = "年末"
End Select

' 振替休日
' 「国民の祝日」が日曜日に当たるときは、その日後においてその日に最も近い「国民の祝日」でない日を休日とする
' なので「国民の祝日」の翌日が月曜日だったときも、振替休日となる
Select Case Serial
  Case DateValue(YYYY & "/1/1") + 1
    If Weekday(Serial) = vbMonday Then fSubstitute = True
  Case DateValue(YYYY & "/2/11") + 1
    If Weekday(Serial) = vbMonday Then fSubstitute = True
  Case DateValue(YYYY & "/2/23") + 1
    If YYYY > 2019 And Weekday(Serial) = vbMonday Then fSubstitute = True
  Case DateValue(YYYY & "/3/" & Int(20.8431 + 0.242194 * (YYYY - 1980) - Int((YYYY - 1980) / 4))) + 1
    If Weekday(Serial) = vbMonday Then fSubstitute = True
  Case DateValue(YYYY & "/4/29") + 1
    If Weekday(Serial) = vbMonday Then fSubstitute = True
  Case DateValue(YYYY & "/5/5") + 1
    ' 5月5日が日曜~火曜日だったら、振替休日確定
    If vbSunday <= Weekday(DateValue(YYYY & "/5/5")) And Weekday(DateValue(YYYY & "/5/5")) <= vbTuesday Then fSubstitute = True
  Case DateValue(YYYY & "/9/" & Int(23.2488 + 0.242194 * (YYYY - 1980) - Int((YYYY - 1980) / 4))) + 1
    If Weekday(Serial) = vbMonday Then fSubstitute = True
  Case DateValue(YYYY & "/11/3") + 1
    If Weekday(Serial) = vbMonday Then fSubstitute = True
  Case DateValue(YYYY & "/11/23") + 1
    If Weekday(Serial) = vbMonday Then fSubstitute = True
  Case DateValue(YYYY & "/12/23") + 1
    If YYYY < 2019 And Weekday(Serial) = vbMonday Then fSubstitute = True
End Select
If fSubstitute = True Then
  DayName = "振替休日"
End If

' 2020年特殊対応
Select Case Serial
  Case DateValue("2020/7/20")
    DayName = "平日"
  Case NumWeek(2020, 10, 2)
    DayName = "平日"
  Case DateValue("2020/8/11")
    DayName = "平日"
  Case DateValue("2020/7/23")
    DayName = "海の日"
  Case DateValue("2020/7/24")
    DayName = "スポーツの日"
  Case DateValue("2020/8/10")
    DayName = "山の日"
  Case DateValue("2020/9/22")
    DayName = "秋分の日"
End Select

' 祝日値判定
If DayName = "平日" Then
  ' ## 曜日選択 #####
  If IsDate(Serial) Then
  Select Case Weekday(Serial)
  Case vbSunday
    RV = vbSunday
    DayName = "休日"
  'Case vbMonday
  ' RV = vbMonday
  'Case vbTuesday
  ' RV = vbTuesday
  'Case vbWednesday
  ' RV = vbWednesday
  'Case vbThursday
  ' RV = vbThursday
  'Case vbFriday
  ' RV = vbFriday
  Case vbSaturday
    RV = vbSaturday
    DayName = "休日"
  End Select

  'RV = Weekday(Serial)
  Else
    Exit Function
  End If
Else
  RV = 8
End If

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

Select Case RVFlag
  Case 0
    chkHoliday = RV
  Case 1
    chkHoliday = DayName
End Select

End Function

Public Function NumWeek(Year, Month, Weeks)
' 参考資料
' http://www.relief.jp/itnote/archives/003241.php

Dim MonNum As Byte
Dim FirstMonDay As Date

' 第1週目の月曜日を求める
Select Case Weekday(DateValue(Year & "/" & Month & "/1"))
  Case vbSunday ' 1
    MonNum = 1
  Case vbMonday ' 2
    MonNum = 0
  Case vbTuesday ' 3
    MonNum = 6
  Case vbWednesday ' 4
    MonNum = 5
  Case vbThursday ' 5
    MonNum = 4
  Case vbFriday ' 6
    MonNum = 3
  Case vbSaturday ' 7
    MonNum = 2
End Select
FirstMonDay = DateValue(Year & "/" & Month & "/" & 1 + MonNum)

' 第1週目月曜日から何週間後なのか、シリアル値を出力
NumWeek = DateValue(Year & "/" & Month & "/" & 1 + MonNum) + 7 * (Weeks - 1)

End Function