9
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

DelphiAdvent Calendar 2020

Day 21

Delphi で "指定した日が祝日かどうか" を内閣府のデータを使って調べる

Last updated at Posted at 2020-12-20

#はじめに
以前カレンダーの壁紙を制作しようと思い立ち,祝祭日をどのように決めるかを調べていたら,
ht_dekoさんの記事で以下の記事をがありました。

Delphi で "指定した日が祝日かどうか" を調べる @ht_deko
https://qiita.com/ht_deko/items/a0cbbbd899c1c016ae67

春分の日や秋分の日を計算で出すという便利な関数ですが,記事の最後に**「祝日は祝日テーブルに登録するのがベストだと思われます。」**と書かれておりました。春分の日と秋分の日が国立天文台の発表を待たないと確定せず,それは1年先のものまでしかわからないということに原因があるのだということが分かりました。
また,2021年についてみてみると東京オリンピックの関係で祝日が動いていています(2020/11/27参院可決)。これをいちいちプログラムで変更するのは面倒だなと思いました。

大臣官房総務課 国民の祝日について
https://www8.cao.go.jp/chosei/shukujitsu/gaiyou.html
令和3年(2021年)の祝日について
令和3年(2021年)に限り、「海の日」は7月22日に、「スポーツの日」は7月23日に、「山の日」は8月8日(※)になります。
平成三十二年東京オリンピック競技大会・東京パラリンピック競技大会特別措置法等の一部を改正する法律の施行に伴い、改正後の令和三年東京オリンピック競技大会・東京パラリンピック競技大会特別措置法(平成27年法律第33号)第32条第2項の規定に基づき、令和3年(2021年)における海の日、スポーツの日及び山の日は上記の通りとなります。
(※)国民の祝日に関する法律(昭和23年法律第178号)第3条第2項の規定に基づき、8月9日は休日となります。

そこで,公式の休日の情報を取得しておいて,@ht_dekoさんの作られた
function IsSpecialHoliday(ADate: TDate; var AName: string): Boolean;
と仕様を合わせた関数を作ろうと考えました。

#データフォーマット

DATA●GO.JP データカタログサイト
https://www.data.go.jp/data/dataset/cao_20190522_0002

によると,昭和30年(1955年)から令和2年(2020年)国民の祝日等(いわゆる振替休日等を含む)(csv形式:19KB)は2021年12月現在で,
https://www8.cao.go.jp/chosei/shukujitsu/syukujitsu.csv
より公開されています。改正後の令和三年東京オリンピック競技大会・東京パラリンピック競技大会特別措置法にも対応しています。
なお,ライセンスはCC-BYとなっております。

CC BY 4.0
https://creativecommons.org/licenses/by/4.0/deed.ja

営利目的でも利用できますね。クレジット表示は必要です。
ダウンロードして分かったのですが,Shift-JISでエンコードされており,半角カンマで区切られた空白のない文字列です。
日付のフォーマットは yyyy/m/d です。例えば以下のようになっています。

2021/11/23,勤労感謝の日

休日全てのデータがあり振替休日休日とだけ書かれています。

#ロジック
休日祭日のデータを取得しファイルに保存する関数と,ファイルに保存された休日祭日のデータを利用して指定した日が休日祭日かどうかを返す関数を実装します。
これらを一つのユニットにまとめて,

  • 起動時に保存されたファイルがあればテーブルに読みだし,
  • データが取得された場合にはそれを更新し,
  • 休日祭日かどうかを返す関数は読みだされたデータを利用するようにして,
  • 終了時にテーブルを開放する

ことにしました。
このデータをTHttpClient(XE8以降で実装された命令)で取得し,指定された場所に保存します。THttpClientはOSの機能でhttpsにも簡単にアクセスできるので便利ですね。シンプルに実装しました。非同期にする場合にはHOSOKAWA @pik様の

THttpClient の落とし穴(解決編)
https://qiita.com/pik/items/95dfebdb659b30918196

がありますので,こちらを参考にしてください。

#コード

p_kato_Holiday.pas
unit p_kato_Holiday;

interface

function SpecialHolidayGetFromHTTP:boolean; // 祝祭日ファイルを更新する ファイルがない場合には必ず実行すること
function IsSpecialHoliday(ADate: TDate; var AName: string): Boolean; // ADateが祝祭日かを返す AName:祝祭日名

implementation

uses
  System.Classes, System.SysUtils, System.Net.HttpClient,
  Generics.Collections;

type
  // 休日祭日のテーブル構造
  TSpecialHoliday=record
    Date:TDate;  // 日付
    Name:string; // 祝日名
  end;

var
  SpecialHolidays:TList<TSpecialHoliday>; // 休日祭日のテーブル 起動時に生成され終了時に破棄される

const
  SpecialHolidayFile='SpecialHoliday.txt'; // 祝日を保存するファイル 特殊フォルダにする場合は変数にしてinitialization時に取得するように変更する

const
  CAOURL='https://www8.cao.go.jp/chosei/shukujitsu/syukujitsu.csv';
  // https://www.data.go.jp/data/dataset/cao_20190522_0002 内閣府データカタログサイトより取得

function GetURLAsString(const AURL: string): string; // URLで指定したファイルの内容を返す
(*
 System.Net.HttpClient.THTTPClient
 http://docwiki.embarcadero.com/Libraries/ja/System.Net.HttpClient.THTTPClient
 山本隆の開発日誌 TNetHTTPRequest/TNetHTTPClientでWebサーバーにアクセスする
 https://www.gesource.jp/weblog/?p=7090
*)
var
  ResponseContent: TMemoryStream;
  HTTPClient:THttpClient;
  st:TStringList;
begin
  Result:='';
  HTTPClient:=THttpClient.Create;
  HTTPClient.AllowCookies := True;
  ResponseContent := TMemoryStream.Create;
  st:=TStringList.Create;
  try
    HTTPClient.Get(AURL, ResponseContent);
    st.LoadFromStream(ResponseContent,TEncoding.GetEncoding('Shift_JIS')); // 2020/12/01現在データはSJIS
    Result:=st.Text;
  finally
    st.Free;
    ResponseContent.Free;
    HTTPClient.Free;
  end;
end;

function StrToDate(s:string;var ADate:TDate):boolean;// YYYY/MM/DD を TDateに変換 成功したらTrueを返す
var
  dt:TStringList;
  y,m,d:word;
begin
  Result:=False;
  dt:=TStringList.Create;
  dt.Delimiter:='/';
  dt.StrictDelimiter:=True;
  dt.DelimitedText:=s;
  if dt.Count=3 then begin
    y:=StrToIntDef(dt[0],0);
    m:=StrToIntDef(dt[1],0);
    d:=StrToIntDef(dt[2],0);
    try
      ADate:=EncodeDate(y,m,d);
      Result:=True;
    finally
    end;
  end;
  dt.Free;
end;

function SpecialHolidayGet(AStringList:TStringList):boolean; // 祭日休日情報の取得
var
  i:integer;
  st:TStringList;
  sd:TSpecialHoliday;
begin
  SpecialHolidays.Clear;
  Result:=True;
  if AStringList.Count>1 then begin
    st:=TStringList.Create;
    st.Delimiter:=',';
    st.StrictDelimiter:=True;
    for i:=1 to AStringList.Count-1 do begin
      st.DelimitedText:=AStringList[i];
      if st.Count=2 then begin
        if StrToDate(st[0],sd.Date) then begin // YYYY/MM/DD を TDateに変換
          sd.Name:=st[1];
          SpecialHolidays.Add(sd);
        end else begin
          Result:=False;
        end;
      end;
    end;
    st.Free;
  end;
end;

function SpecialHolidayGetFromHTTP:boolean; // ファイルを保存できたかを確認する
var
  s:string;
  st:TStringList;
begin
  Result:=False;
  s:=GetURLAsString(CAOURL);
  if s<>'' then begin
    st:=TStringList.Create;
    try
      st.Text:=s;
      st.SaveToFile(SpecialHolidayFile,TEncoding.UTF8);
      if s<>'' then begin
        Result:=SpecialHolidayGet(st); // 祭日休日情報の取得
      end;
    finally
      st.Free;
    end;
  end;
end;

function SpecialHolidayGetFromFile:boolean; // ファイルを保存できたかを確認する
var
  st:TStringList;
begin
  Result:=False;
  if FileExists(SpecialHolidayFile) then begin
    st:=TStringList.Create;
    try
      st.LoadFromFile(SpecialHolidayFile,TEncoding.UTF8);
      if st.Text<>'' then begin
        Result:=SpecialHolidayGet(st); // 祭日休日情報の取得
      end;
    finally
      st.Free;
    end;
  end;
end;

//

function IsSpecialHoliday(ADate: TDate; var AName: string): Boolean;
var
  i:integer;
begin
  for i:=0 to SpecialHolidays.Count-1 do begin
    if SPecialHolidays[i].Date=ADate then begin
      AName :=SPecialHolidays[i].Name;
      Result:=True;
      exit;
    end;
  end;
  AName :='';
  Result:=False;
end;

initialization
  SpecialHolidays:=TList<TSpecialHoliday>.Create; // 休日祭日配列を生成
  SpecialHolidayGetFromFile;                      // 保存されていれば休日祭日配列ファイルを読み込む
finalization
  SpecialHolidays.Free;                           // 休日祭日配列を破棄
end.

#使い方
利用するフォームで
uses
p_kato_Holidays;

を記述し,休日祭日を取得してファイルに保存するときに
SpecialHolidayGetFromHTTP;
を実行します。ファイルをまだ持っていないときには必ず実行するようにしてください。
IsSpecialHoliday(ADate: TDate; var AName: string): Boolean;
 でADateが祝日休日かどうかを返します。祝日休日ならAName祝日祭日名が入ります。振替休日のときは休日が入ります。
 祝日でも休日でもないなら,ANameには空白が入ります。

皆様のお役に立てば幸いです。

#おまけ
カレンダーコンポーネントの文字色を設定するには
@igy様の カレンダーコンポーネントの文字色を設定 https://qiita.com/igy/items/54867fffdb6dac19fa0b にその方法が書かれています。ぜひご確認ください。

#参考資料
この記事を書くために参考にした資料で本文でご紹介できなかったものは以下の通りです。
 08_THttpClient または TNetHttpClient を使用する接続処理 Mr.XRAY
 http://mrxray.on.coocan.jp/Delphi/plSamples/772_Indy_HTTPGet.htm#08
 山本隆の開発日誌 TNetHTTPRequest/TNetHTTPClientでWebサーバーにアクセスする
 https://www.gesource.jp/weblog/?p=7090

#謝辞
deco(@ht_deco)様 HOSOKAWA(@pik)様 XRAY様 山本隆様 igy様 皆様のおかげでこの原稿を書くことができました。ありがとうございます。

9
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?