ちょっとわけあってカレンダーをホゲっているんですが、祝日管理というのが大変に面倒であることが判明した。 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 秒くらいかかりますかねぇ。ちょっと重いか?
良かったら参考にしてください。あ。もっと綺麗なコード、絶賛募集中です;-)。