Excel で休日祝日判定

〜 2016.8.17 追記〜
6日前に過ぎ去った祝日 "山の日" の対応をしてみた
といっても、記述しているソースコードのみで、添付しているものはまだ未着手……
ついでにいうと、今、MS Office が入ってる端末が全滅しちゃって、検証するには Excel とか買わないといけない感じ。


〜 2015.4.24 追記〜
ごめんなさい!
振替休日に対する考慮が足りてなくて、公開していた VBA モジュールは 2015 年しか対応してませんでした!
(そもそも2015年しか確認してなくて、問題なかったのですが…。今年は G.W. しか振替休日がないという年なんですね。なんで G.W. しか振替休日を対応してなかったのか、振替休日がこんなにも少ない年があるものなのかとか、いろいろびっくり)


ということで、モジュールを修正しました。
国民の祝日」が日曜日に当たるときは、その日後においてその日に最も近い「国民の祝日」でない日を休日とする
とあるので、シリアル値を比較して応答を返す仕様上「国民の祝日」の翌日が月曜日なら、その日は振替休日であるという処理を入れています。
〜 追記ここまで 〜


休日もしくは祝日かどうか判定する Excel VBA マクロを作ってみたので、さくっと公開。
多少、意味のわからない部分*1はあると思うけど、使う分には問題ないもの。


土日だけ判定する方法は、以前(http://d.hatena.ne.jp/KuroNeko666/20140509/1399627659)にも書いたことあるけど、今回は祝日まで考慮したもの。

使い方

  1. 下記 [file:KuroNeko666:JModule_3.ZIP] をダウンロードして、解凍。
  2. 解凍したファイル "JModule.bas" を VBA マクロを使いたい Excel ブックへインポート。
  3. Excel で =chkHoliday(シリアル値) を入力。
  4. シリアル値に対応した数字が返ってくるので、再利用。


[file:KuroNeko666:JModule_3.ZIP]
[file:KuroNeko666:JModule_2.zip] ← 旧バージョン
[file:KuroNeko666:JModule.zip] ← 旧バージョン


※インポート方法は記載しません。
※利用するにはマクロを有効にする必要があります。


例えば関数としてセルに =chkHoliday(DATE(2015,3,28)) と入力すれば、2015年3月28日が平日であると応答してくれます。
=chkHoliday(DATE(2015,1,1)) と入れれば、祝日なのでセルには 8 が出ているはず。


戻り値の定義としては、こんな感じです。
0 = 平日
1 = 日曜日
7 = 土曜日
8 = 祝日


なので、一番簡単な使い方をすると 0 だったら出勤日、0 以外だったら休日といった方法で幸せになれます。


A1に 2015/3/28 を入力。
A2に =if(chkHoliday(A1)=0,"出勤日","休み!") を入力。
表示は "休み!"


ついでに、2つめのオプションへ 1 を入れると "平日" か "(祝日名)" を返すようになります。


A1に 2015/1/1 を入力。
A2に =chkHoliday(A1,1) を入力。
表示は "元旦" となります。


アルゴリズム

ここから下は、技術的な話になるので文章の書き方を微妙に変えて構成。


アルゴリズム的には、こんな感じ。
変数 DayName に、デフォルト名 "平日" をセット。
引数のシリアル値と、祝日に対応するYYYY年MM月DD日といったシリアル値とを比較して、同じならば変数 DayName に祝日の名前をセット。
このため、変数 DayName がデフォルト値 "平日" であれば、それはそのまま祝日以外になるので、シリアル値を Weekday 関数でチェックして、特定の曜日だけ、変数 RV に値をセット。
"平日" 以外であれば、変数 RV に 8 をセット。
最後に、オプションで戻り値フラグが立っていなければ素直に変数 RV を返し、戻り値フラグが 1 だったら変数 DayName の中身を返す。


キモは DateValue 関数で、日付が決まっているものはシリアル値の年部分を使って該当年を作り出すと、シリアル値同士で比較しやすくなる。
直感的な修正は簡単なはず。
春分/秋分の日は、微妙に不安が残りますが…たぶん計算上は大丈夫なはず。
海の日や敬老の日といった第x月曜日は、何度も同じ計算方法を使いまわすので関数を別に作り、年,月,週の三つを引数に与えれば対象月曜日のシリアル値が戻ってくるように。


ちなみに Excel のシリアル値は、1900年1月0日を基点にして、1日の経過毎に整数 1 を加えて算出する。
シリアル値が 1 ならば 1900年1月1日、シリアル値が 42090 ならば 2015年3月27日になる。


一応ソースコードも記載するので、不安な人は下記からコピペでどうぞ。
年末年始とか休日が水曜日とか、カスタマイズは簡単なはず。


〜 2016.12.10 シリアル値の変数名がおかしかった分を修正〜
〜 2019.6.1 平成 → 令和への改元対応(以下のソースコードのみ)〜
〜 2019.12.5 2020年の特殊な祝日変更に対応〜

ソース

Option Explicit

Public Function chkHoliday(Serial, Optional RVFlag As Byte = 0)
' #########################################################################
' # 呼び出されると、引数のSerialから休日/祝日判定フラグを返すマクロ
' #########################################################################

' 戻り値の定義
' 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
  'Case vbMonday
  ' RV = vbMonday
  'Case vbTuesday
  ' RV = vbTuesday
  'Case vbWednesday
  ' RV = vbWednesday
  'Case vbThursday
  ' RV = vbThursday
  'Case vbFriday
  ' RV = vbFriday
  Case vbSaturday
    RV = vbSaturday
  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

応用編

応用で、A1に 2015 B1に 3 を入力。
A3 に =DATE(A1,B1,1) を入力。
A5 から A33 まで、A4 をコピー。
ここまでで、以下状態のはず。



A2 に "日" を入力、B2に "フラグ" を入力。
A3 以下、選択して右クリック → セルの書式設定(F) を選択。
表示形式タブでユーザー定義を選択して、種類に d を入力。


すると、こんな感じになるはず。


で、A3 から B33 まで選択した後、ホームタブの[条件付き書式設定]から[新しいルール]を選択。
[新しい書式ルール]では、数式を使用して、書式設定するセルを決定を選択。
ルールの内容に =$B3=1 を入れて[書式]ボタンをクリック。
書式は、自分の好きなように設定する。

OK を押すと、もう、該当のセルが設定した書式で色が変わっているはず。
ルールの内容に =$B3=7 と =$B3=8 の場合の書式を、同じように設定する。


最終的に、書式設定はこんな感じになれば良い。

※この画面自体は、ホームタブの[条件付き書式設定]から[ルールの管理]を選択、さらに書式ルールの表示で[このワークシート]を選択したもの。


Excel 上は、こんな風になっているはず。


指定に $ を付けると、必ずその行、もしくは列を参照して…という意味になるので、この場合は 各行必ず B 列を参照して条件付き書式の表示をすることとしている。
間違って $B$3 とかすると、B3 しか参照しないので全体が同じ画面になる。


あとは応用で、もっと手を加えていけば自分の好み、上司の好みで作れる。


実は書式設定で chkHoliday を使えたりもするんだけど、なぜか処理が異様に重くなったりするので推奨できない。

*1:変数に入れてるのに、なんで使いまわさなかったんだっけ…