Edited at

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


はじめに

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


ロジック

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


いきなりコード

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

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~
// 第2月曜日(ハッピーマンデー)
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~
// 第3月曜日 (五輪祝日移動法 により 2020 年は 23 日)
if (dYear >= 2003) then
begin
if ((dYear = 2020) and (dDay = 23)) or
((dYear <> 2020) and (ADate = FreqOfWeek(dYear, dMonth, 3, 2))) then
begin
result := True;
AName := '海の日';
Exit;
end;
end;
// '体育の日③' 2020 (五輪祝日移動法)
if (dYear = 2020) and (dDay = 24) then
begin
result := True;
AName := 'スポーツの日';
Exit;
end;
end;
8:
begin
// '山の日' 2016~
// 第3月曜日 (五輪祝日移動法 により 2020 年は 10 日)
if ((dYear >= 2016) and (dYear <> 2020) and (dDay = 11)) or ((dYear = 2020) and (dDay = 10)) 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~
// 第3月曜日
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~
// 第2月曜日(ハッピーマンデー) (五輪祝日移動法 により 2020 年を除く)
if (dYear >= 2000) and (dYear <> 2020) 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;


説明

概要については 内閣府の「国民の祝日」について を読む事をオススメします。

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

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

祝日
例年
2020 年

海の日
7月第3月曜日
7/23

体育の日
10月第2月曜日
7/24

山の日
8月第3月曜日
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


おわりに

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

なお、VBA 版として藤田聖二氏の作られた "指定した日が祝日か否かを調べるとともに、祝日名を求める関数" もあります。