Perl において、週末や祝祭日、振替休日を考慮して n 営業日前後の日付を求めることができましたが、続いて、日付 X から日付 Y までの営業日を数えるための覚書きです。
#!/usr/bin/perl
use v5.10;
use Calendar;
use Calendar::Japanese::Holiday;
### 任意指定の休日 MM/DD/YYYY
my %special_holiday = map{ $_ => 1 } qw{
12/29/2015
12/30/2015
12/31/2015
01/04/2016
};
### $_[0] から $_[1] まで何営業日あるか
sub business_days {
my $dt1 = shift || die;
my $dt2 = shift || die;
# 月末/年末をまたぐ計算が面倒なので Calendar モジュールを使う
my( $y, $m, $d ) = $dt1 =~ /^(\d+)\D+(\d+)\D+(\d+)/;
my $c = Calendar->new_from_Gregorian( $m, $d, $y );
# 日付比較が便利なので Calendar モジュールを使う
my( $y2, $m2, $d2 ) = $dt2 =~ /^(\d+)\D+(\d+)\D+(\d+)/;
my $c2 = Calendar->new_from_Gregorian( $m2, $d2, $y2 );
my $days = 0;
while( my $offset_dir = $c2 <=> $c ){
$c += $offset_dir;
# 土日はカウントしない
next if $c->weekday == 0; # 日曜
next if $c->weekday == 6; # 土曜
# 日本の祝祭日はカウントしない(振替休日も考慮する)
next if isHoliday( $c->year, $c->month, $c->day, 1 );
# 特別休日はカウントしない
next if $special_holiday{$c};
$days += $offset_dir;
}
$days;
}
# 年末年始で動作確認
say business_days( '2015-12-31', '2016-01-07' ); # +3
say business_days( '2016/01/04', '2015-12-24' ); # -3
# 成人の日前後(振替休日あり)で動作確認
say business_days( '2016-01-08', '2016-01-14' ); # +3
say business_days( '2016-01-13', '2016-01-07' ); # -3
メモ
- 基本ロジックは
dt2offset
と同じです。
- 土日は単純に曜日で判定します
- 日本の祝祭日の判定には、Calendar::Japanese::Holiday モジュールを使用します
- 創立記念日など任意の休日を配列(special_holiday)に設定しておきます
- 基準日や目的の日付が営業日でない場合、その分だけ日数が減ります
- 20~24 行目
- 日付の増減に際して、月末・年末・うるう年の面倒を避けるため、Calendar オブジェクトにしておきます。
- 27~28 行目
- Calendar オブジェクトの
<=>
演算子で、基準の日付(c)と目的の日付(c2)の前後関係を求めます。例えば、c2 が c より後日の場合、offset_dir は 1 になります。c を offset_dir 方向に動かしていき、c と c2 が等しくなった時、ループを抜けます。
- 33 行目
- Calendar::Japanese::Holiday::isHoliday で振替も考慮した祝祭日の判定。isHoliday 関数は、その日が祝祭日だった場合、その名前を返します。振替休日だった場合、
振替
と返します。
- 35 行目
- Calendar のインスタンスをスカラ コンテクストで評価すると、
MM/DD/YYYY
形式で日付を返すので、%special_holiday に特別休日の指定がないか調べています。
- 36 行目
- これらの条件に当てはまらない場合にのみ、営業日数のカウントを進めます。