Delphi で "指定した日が祝日かどうか" を調べる

# はじめに

指定した日が祝日かどうかを調べる関数を Delphi で書いてみました...正確には[昔から書いてあった](https://ht-deko.com/tech004.html)のですが、オリンピック絡みで祝日が変更されるという事だったので、それを反映してみました。

# ロジック

現行の祝日法で制定年が一番古いのが 1948 年ですので、それ以降の日付でしか使えません。充分だとは思いますが...。


## いきなりコード

ソースコードです。IsSpecialHoliday() の ADate に日付を入れて実行すると、祝日であれば True を返し、AName に祝日名が入ります。

```pascal
function IsSpecialHoliday(ADate: TDate; var AName: string): Boolean;
// ------------------------------------------------------------
// http://www8.cao.go.jp/chosei/shukujitsu/gaiyou.html
// http://koyomi.vis.ne.jp/
// http://www.asahi-net.or.jp/~CI5M-NMR/misc/equinox.html#Rule
// ------------------------------------------------------------
// ADateが祝日かどうかを返す。
// 祝日=True,祝日ではない=False
// AName には祝日の名前を返す
var
  DName: string;
  i:Integer;
  {FreqOfWeek Begin}
  function FreqOfWeek(AYear, AMonth: Word; AWeekNo, ADayOfWeeek: Byte): TDateTime;
    // AYear年AMonth月の第AWeekNo「ADayOfWeeek曜日」の日付を返す
    // ADayOfWeeek　日曜日=1..土曜日=7
  var
    dDay: Word;
    dDoW: Word;
    dWeekNo: Byte;
  begin
    dDoW := DayOfWeek(EncodeDate(AYear, AMonth, 1));
    dWeekNo := AWeekNo;
    if ADayOfWeeek >= dDoW then
      dWeekNo := dWeekNo - 1;
    dDay := (dWeekNo * 7) + (ADayOfWeeek - dDoW) + 1;
    result := EncodeDate(AYear, AMonth, dDay);
  end;
  {FreqOfWeek End}
  {LeapYearCount Begin}
  function LeapYearCount(SYear, EYear: Word): Integer;
    // SYearからEYear迄に何回閏年があるかを返す
  var
    i: Integer;
    Cnt: Integer;
  begin
    Cnt := 0;
    for i := sYear to eYear do
      begin
        if (i mod 4) <> 0 then
          Continue;
        if IsLeapYear(i) then
          Inc(Cnt);
      end;
    result := Cnt;
  end;
  {LeapYearCount End}
  {VernalEquinox End}
  function VernalEquinox(AYear: Word): TDateTime;
    // Ayearの春分の日を求める
  var
    dDay: Word;
  begin
    dDay := Trunc((21.147 + ((AYear - 1940) * 0.2421904) - (LeapYearCount(1940, AYear) - 1)));
    result := EncodeDate(AYear, 3, dDay);
  end;
  {VernalEquinox End}
  {AutumnalEquinox End}
  function AutumnalEquinox(AYear: Word): TDateTime;
    // Ayearの秋分の日を求める
  var
    dDay: Word;
  begin
    case AYear of
      1979:
        dDay := 24;
    else
      dDay := Trunc((23.5412 + ((AYear - 1940) * 0.2421904) - (LeapYearCount(1940, AYear) - 1)));
    end;
    result := EncodeDate(AYear, 9, dDay);
  end;
  {AutumnalEquinox End}
  {_IsSpecialHoliday Begin}
  function _IsSpecialHoliday(ADate: TDate; var AName: string): Boolean;
    // ADateが祝日かどうかを返す。
    // 祝日=True,祝日ではない=False
    // AName には祝日の名前を返す
    // '国民の休日'はここでは算出されない
  var
    dYear,
      dMonth,
      dDay: Word;
  begin
    AName := '';
    result := False;
    DecodeDate(ADate, dYear, dMonth, dDay);
    case dMonth of
      1:
        begin
          // '元日' 1948～
          if (dYear >= 1948) and (dDay = 1) then
            begin
              result := True;
              AName := '元日';
              Exit;
            end;
          // '成人の日①' 1948～1999
          if (dYear >= 1948) and (dYear <= 1999) and (dDay = 15) then
            begin
              result := True;
              AName := '成人の日';
              Exit;
            end;
          // '成人の日②' 2000～
          // 第２月曜日(ハッピーマンデー)
          if (dYear >= 2000) then
            begin
              if ADate = FreqOfWeek(dYear, dMonth, 2, 2) then
                begin
                  result := True;
                  AName := '成人の日';
                  Exit;
                end;
            end;
        end;
      2:
        begin
          // '建国記念の日' 1967～
          if (dYear >= 1967) and (dDay = 11) then
            begin
              result := True;
              AName := '建国記念の日';
              Exit;
            end;
          // '天皇誕生日②' 2020～
          if (dYear >= 2020) and (dDay = 23) then
            begin
              result := True;
              AName := '天皇誕生日';
              Exit;
            end;
          // ※昭和天皇の大喪の礼(1989/02/24)
          if (dYear = 1989) and (dDay = 24) then
            begin
              result := True;
              AName := '昭和天皇の大喪の礼';
              Exit;
            end;
        end;
      3:
        begin
          // '春分の日' 1949～
          if (dYear >= 1949) then
            begin
              if ADate = VernalEquinox(dYear) then
                begin
                  result := True;
                  AName := '春分の日';
                  Exit;
                end;
            end;
        end;
      4:
        begin
          // '天皇誕生日' 1948～1988
          if (dYear >= 1948) and (dYear <= 1988) and (dDay = 29) then
            begin
              result := True;
              AName := '天皇誕生日';
              Exit;
            end;
          // 'みどりの日①' 1989～2006
          if (dYear >= 1989) and (dYear <= 2006) and (dDay = 29) then
            begin
              result := True;
              AName := 'みどりの日';
              Exit;
            end;
          // '昭和の日' 2007～
          if (dYear >= 2007) and (dDay = 29) then
            begin
              result := True;
              AName := '昭和の日';
              Exit;
            end;
          // ※皇太子明仁親王の結婚の儀(1959/04/10)
          if (dYear = 1959) and (dDay = 10) then
            begin
              result := True;
              AName := '皇太子明仁親王の結婚の儀';
              Exit;
            end;
        end;
      5:
        begin
          // '天皇の即位' 2019/05/01
          if (dYear = 2019) and (dDay = 1) then
            begin
              result := True;
              AName := '天皇の即位';
              Exit;
            end;
          // '憲法記念日' 1948～
          if (dYear >= 1948) and (dDay = 3) then
            begin
              result := True;
              AName := '憲法記念日';
              Exit;
            end;
          // 'みどりの日②' 2007～
          if (dYear >= 2007) and (dDay = 4) then
            begin
              result := True;
              AName := 'みどりの日';
              Exit;
            end;
          // 'こどもの日' 1948～
          if (dYear >= 1948) and (dDay = 5) then
            begin
              result := True;
              AName := 'こどもの日';
              Exit;
            end;
        end;
      6:
        begin
          // ※皇太子徳仁親王の結婚の儀(1993/06/09)
          if (dYear = 1993) and (dDay = 9) then
            begin
              result := True;
              AName := '皇太子徳仁親王の結婚の儀';
              Exit;
            end;
        end;
      7:
        begin
          // '海の日①' 1996～2002
          if (dYear >= 1996) and (dYear <= 2002) and (dDay = 20) then
            begin
              result := True;
              AName := '海の日';
              Exit;
            end;
          // '海の日②' 2003～
          // 第３月曜日 (五輪祝日移動法 により 2020 年は 23 日、2021 年は 22 日)
          if (dYear >= 2003) then
            begin
              if ((dYear =  2020) and (dDay = 23)) or
                 ((dYear =  2021) and (dDay = 22)) or
                 ((dYear <> 2020) and (dYear <> 2021) and (ADate = FreqOfWeek(dYear, dMonth, 3, 2))) then
                begin
                  result := True;
                  AName := '海の日';
                  Exit;
                end;
            end;
          // '体育の日③' 2020 (五輪祝日移動法)
          // '体育の日④' 2021 (五輪祝日移動法)
          if ((dYear = 2020) and (dDay = 24)) or
             ((dYear = 2021) and (dDay = 23)) then
            begin
              result := True;
              AName := 'スポーツの日';
              Exit;
            end;
        end;
      8:
        begin
          // '山の日' 2016～
          // 第３月曜日 (五輪祝日移動法 により 2020 年は 10 日、2021 年は 8 日)
          if ((dYear = 2020) and (dDay = 10)) or
             ((dYear = 2021) and (dDay =  8)) or
             ((dYear >= 2016) and (dYear <> 2020) and (dYear <> 2021) and (dDay = 11)) then
            begin
              result := True;
              AName := '山の日';
              Exit;
            end;
        end;
      9:
        begin
          // '敬老の日①' 1966～2002
          if (dYear >= 1966) and (dYear <= 2002) and (dDay = 15) then
            begin
              result := True;
              AName := '敬老の日';
              Exit;
            end;
          // '敬老の日②' 2003～
          // 第３月曜日
          if (dYear >= 2003) then
            begin
              if ADate = FreqOfWeek(dYear, dMonth, 3, 2) then
                begin
                  result := True;
                  AName := '敬老の日';
                  Exit;
                end;
            end;
          // '秋分の日' 1948～
          if (dYear >= 1948) then
            begin
              if ADate = AutumnalEquinox(dYear) then
                begin
                  result := True;
                  AName := '秋分の日';
                  Exit;
                end;
            end;
        end;
      10:
        begin
          // '体育の日①' 1966～1999
          if (dYear >= 1966) and (dYear <= 1999) and (dDay = 10) then
            begin
              result := True;
              AName := '体育の日';
              Exit;
            end;
          // ※即位礼正殿の儀(2019/10/22)
          if (dYear = 2019) and (dDay = 22) then
            begin
              result := True;
              AName := '即位礼正殿の儀';
              Exit;
            end;
          // '体育の日②' 2000～
          // 第２月曜日(ハッピーマンデー) (五輪祝日移動法 により 2020 年と 2021 年を除く)
          if (dYear >= 2000) and (dYear <> 2020) and (dYear <> 2021) then
            begin
              if ADate = FreqOfWeek(dYear, dMonth, 2, 2) then
                begin
                  result := True;
                  if (dYear < 2020) then
                    AName := '体育の日'
                  else
                    AName := 'スポーツの日';
                  Exit;
                end;
            end;
        end;
      11:
        begin
          // '文化の日' 1948～
          if (dYear >= 1948) and (dDay = 3) then
            begin
              result := True;
              AName := '文化の日';
              Exit;
            end;
          // '勤労感謝の日' 1948～
          if (dYear >= 1948) and (dDay = 23) then
            begin
              result := True;
              AName := '勤労感謝の日';
              Exit;
            end;
          // ※即位礼正殿の儀(1990/11/12)
          if (dYear = 1990) and (dDay = 12) then
            begin
              result := True;
              AName := '即位礼正殿の儀';
              Exit;
            end;
        end;
      12:
        begin
          // '天皇誕生日①' 1948～2018
          if (dYear >= 1989) and (dYear <= 2018) and (dDay = 23) then
            begin
              result := True;
              AName := '天皇誕生日';
              Exit;
            end;
        end;
    end;
  end;
  {_IsSpecialHoliday End}
begin
  result := False;
  AName := '';
  if _IsSpecialHoliday(ADate, DName) then
    begin
      result := True;
      AName := DName;
    end
  else if (ADate >= EncodeDate(1973, 4, 12)) and (DayOfWeek(ADate) = 2) and
          _IsSpecialHoliday(ADate - 1, DName) then
    begin
      // 振替休日①　1973/04/12以降
      // 日曜日と祝祭日が重なった場合には'振替休日'となる
      result := True;
      AName := '振替休日';
    end
  else if (ADate >= EncodeDate(1988, 5, 4)) and (DayOfWeek(ADate) <> 1) and
          _IsSpecialHoliday(ADate - 1, DName) and _IsSpecialHoliday(ADate + 1, DName) then
    begin
      // 国民の休日 1988/05/04以降
      // 祝日と祝日に挟まれた平日は'国民の休日'となる。
      result := True;
      AName := '国民の休日';
    end
  else if (ADate >= EncodeDate(2008, 5, 6)) and (DayOfWeek(ADate) <> 1) and
          _IsSpecialHoliday(ADate - DayOfWeek(ADate) + 1, DName) then
    begin
      // 振替休日②　2008/05/06以降
      // '祝日'が日曜日に当たるときは、その日後においてその日に最も近い'祝日'でない日を休日とする
      result := True;
      AName := '振替休日';
      for i:=1 to DayOfWeek(ADate) - 2 do
        begin
          if not _IsSpecialHoliday(ADate - i, DName) then
            begin
              result := False;
              AName := '';
              Break;
            end;
        end;
    end;
end;
```

## 説明
概要については [内閣府の「国民の祝日」について](http://www8.cao.go.jp/chosei/shukujitsu/gaiyou.html) を読む事をオススメします。

コード的には特殊な事をしているわけではなく、**case** 文と **if** 文で条件分岐しているだけです。昔からある構文と関数しか使っていないので、かなり古い Delphi でもコンパイルが通ると思います。

2020 年が特殊で、いわゆる**五輪祝日移動法**により、幾つかの祝日が特例で移動になっています。

| 祝日 | 例年 | 2020 年 |
|:----|:----:|:-------:|
|海の日|7月第3月曜日|7/23|
|体育の日|10月第2月曜日|7/24|
|山の日|8/11|8/10|

そして 2020 年から**体育の日**が**スポーツの日**に名称変更になります。

加えて天皇誕生日が変更になります。

| 祝日 | ～ 2018 年| 2020 年 ～ |
|:----|:----:|:-------:|
|天皇誕生日|12/23|2/23|

**皇室典範特例法**の施行日 (2019/04/30) の関係で、**2019 年には天皇誕生日がありません**。

**天皇の即位の日及び即位礼正殿の儀の行われる日を休日とする法律** (施行日: 2019/12/14) のため、下記の 2 日が祝日となります。

| 祝日 | 2019 年 |
|:----|:----:|
|天皇の即位の日|5/1|
|即位礼正殿の儀|10/22|

よって、下記の 2 日も国民の祝日となります。

| 祝日 | 2019 年 |
|:----|:----:|
|国民の祝日|4/30|
|国民の祝日|5/2|

### 2021 年の五輪祝日移動法

2021 年も、いわゆる**五輪祝日移動法**により、幾つかの祝日が特例で移動になっています。

| 祝日 | 例年 | 2021 年 |
|:----|:----:|:-------:|
|海の日|7月第3月曜日|7/22|
|スポーツの日|10月第2月曜日|7/23|
|山の日|8/11|8/8|

**See also:**

 - [2021年の祝日移動について (首相官邸)](https://www.kantei.go.jp/jp/headline/tokyo2020/shukujitsu.html)


# おわりに

ここまでやっといてなんですが、祝日は年毎に**祝日テーブル**に登録するのがベストだと思われます...**春分の日と秋分の日があるから**です。春分の日と秋分の日の祝日は厳密には計算で求められるものではありません。

なお、VBA 版として藤田聖二氏の作られた ["指定した日が祝日か否かを調べるとともに、祝日名を求める関数"](http://amaranth.velvet.jp/als/jpn/progtip.htm#BAS6) もあります。