5月 122011
 

ちょっとわけあってカレンダーをホゲっているんですが、祝日管理というのが大変に面倒であることが判明した。 perl でホゲっているのでウェブで探したり、モジュールを検討したんだけど、どうもしっくり来ない。祝日が変更になるとソースコードを改変したり、モジュールをインストールしなければならなかったりで、ずっと保守がつきまとう・・。orz。

何かないかなぁ?とか探していたら google カレンダーの API を叩いて祝日をゲットして来る。ってのを見つけたけど XML のパースとか、どーもやることが多すぎる。他に何か無いかなぁ。とか探したり、手段を見つけようとしてます。あ。そー言えば、僕、ical サーバ作ったじゃん。てのを思い出したので、ical サーバから情報持って来られないか調べたけど、ちょっとダメっぽい。

それならば。と思いついたのが、ics ファイルをパースするサブルーチン作ってしまえ。ってことで非常にオオチャクな発想をしてしまったのであります;-)。

まず、Apple のサイトから日本の休日カレンダーを取ってきます。MacOSX のアプリとして iCal があり、それ用に提供している ics ファイルがたくさんある URL があります。

iCal カレンダーライブラリ

ここから日本の祝日用カレンダーを2,3ヶ月に一度 cron で持ってくるようにします。祝日はそーそー更新されることは無いと思うので頻度は低くて大丈夫。

そして、Apple が提供している情報は多分 iCal アプリがある間は公開し続けてくれるだろうと勝手に思っているので安心感がある;-)。

% ftp -a http://ical.mac.com/ical/Japanese32Holidays.ics

  さてと。これで絶えず最新の祝日情報がゲットできました。あとはこれをパースして祝日を抽出すれば良いだけですね;-)。
僕の書いたサンプルコードはこんな感じ。もっと美しくかける方いましたら絶賛募集中;-)。
 
#!/usr/bin/perl
use strict;
use Date::Manip;
# ## main # my $date = shift; if ($date) { my $data = &holiday_check($date); print $data ."\n\n"; } else { for (my $mm = 1;$mm < 13;$mm++) { for (my $dd = 1;$dd < 32;$dd++) { my $date = "2011" . sprintf("%02d",$mm) . sprintf("%02d",$dd); my $data = &holiday_check($date); print $data ."\n" if ($data); } } } exit 1;
# ## 祝日のチェック # sub holiday_check { my $date = shift; my $name = ""; my $day = ""; my $f = 0;
open(CAL,"<./Japanese32Holidays.ics"); while( my $line = <CAL> ) { $line =~ s/\r\n//g; chomp $line; $f = 0; if ($line =~ /^BEGIN:VEVENT/) { while( my $line = <CAL> ) { $line =~ s/\r\n//g; chomp $line; last if ($line =~ /^END:VEVENT/); # 日付が決まっている祝日の場合はそれを利用する if ($line =~ /^(DTSTART;VALUE=DATE:)/) { $line =~ s/$1//; $day = $line; $f = 1 if ($day eq $date); } # 祝日名を取得 if ($line =~ /^(SUMMARY:)/) { $line =~ s/$1//; $name = $line; } # 祝日法で決まっている祝日の情報の取得 if ($line =~ /^RRULE:/) { my $yy = substr($date,0,4); my $mm = ""; my $dd = ""; my @tmp = split(";",$line); foreach my $a (@tmp) { # BYMONTH は何月かの情報 $mm = $1 if ($a =~ /BYMONTH=(.*)/); # BYDAY は月曜日が祝日の時の計算 # 今のところは月曜日しか無いので MO の狙い撃ち # 祝日法が変わったら改修しようね;-)。 if ($a =~ /BYDAY=(.*)MO/) { $dd = $1 ; my @a = ParseRecur("*$yy:$mm:$dd:1:0:0:0"); my $d = substr($a[0],0,8); #print "@a,$a[0] : $date,$d\n"; if ($date eq $d) { $f = 1; last; } } } # BYDAY が無い RRULE の場合は DTSTART の月日を利用する unless ($line =~ /BYDAY=/) { my $datesv = $date; my $d = substr($datesv,4,4); if ($day =~ /$d$/) { $f = 1; last; } } } } last if ($f); } } close(CAL);
my $data = ""; if ($f) { $data = "$date : $name "; } return($data); }

 
このプログラムを CheckHoliday.pl として保存して、実行する場合は以下のような感じ。

 % ./CheckHoliday.pl 20110718
20110718 : 海の日 (Marine Day)

 
スクリプトの引数に YYYYMMDD を指定します。その日が祝日であった場合にはその名前を表示します。日付を指定しない場合は 2011 年の祝日全てを表示します。メインのほうは皆さんの環境に合わせて作ってください。今回はサブルーチンのほうがメインになります。

このスクリプト、ちょっと解説すると以下になります。

1. 基本的には VCALENDAR 形式の ics ファイルをパースします。
2. 振替休日 など、日付が固定しているデータは DTSTART を参照します。
3. 海の日 など、第二、三月曜日が休みな場合は RRULE の BYDAY を見ます。
4. RRULE に BYDAY が無いヤツは国が日付を決めた祝日なので DTSTART の MMDD のみを参照します。

これで祝日の情報を取得します。Date::Manip モジュールの ParseRecur は第何週の日付を返してくれるモジュールでこれが非常に助かりました。

と、言うことで、日付を与えればその日付が祝日であれば値を返す。と言うサブルーチンの完成です。

速度的にみると 20110101 から 20111231 までを調べるとだいたい 1.5 秒くらいかかりますかねぇ。ちょっと重いか?

良かったら参考にしてください。あ。もっと綺麗なコード、絶賛募集中です;-)。

  3 件のコメント to “perl で祝日を特定するサブルーチン。”

  1. Calendar::Japanese::Holiday じゃだめですか?

  2. しろぺんさん。こんにちわ。
    Calendar::Japanese::Holiday でも多分良いとは思うのですが、確か、祝日に変更があった場合はバージョンアップしなければならないですよねぇ。
    今回はカンペキにメンテナンスフリーを目指したかったので、生のデータをどっかから取ってきてそれを利用してサクっとな。としたかったのでありました。
    まぁ、生きているデータの提供先がいつまで対応してくれるんだ? って話は当然あるんですけどねぇf(^^;;。

  3. 会社の人に「会社の休日はどうすんの?」って言われてしまった。確かに年末年始などは会社は休みだねぇ。
    とりあえず、KDE4 の korganizer にカレンダーとして会社の休日を設定。そして、その保存されたファイルが vCalender 形式、つまり ics 形式なので当該の会社の休みの部分の BEGIN を持ってきて、Japanese32Holidays.ics に追加してあげれば OK。みたいな感じかなー。
    んー。アンチョク;-)。
    本当はファイル二つくらいを open できるように改造したほうが良いとは思うんだけどねぇ。Japanese32Holidays.ics は default で処理して、もう一個の、例えば SiteHolidays.ics とか言うファイルが存在していたら open して処理対象とする。とかね。

 コメントを書いてください。

HTML タグが利用できます: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong> <img localsrc="" alt="">

(必須項目)

(必須項目)

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)