読者です 読者をやめる 読者になる 読者になる

さわだのノート

書籍のお仕事に役立つかもしれない思いつきを記録しています。

RSS: 記事の更新情報 Rss Feed

Perlで「X営業日後」の日付を計算する

たまにExcelでスケジュール表を作ったりするのですが、今日から30営業日後の日付などを計算するのが面倒です。カレンダーで日付を数え上げていくのもアホくさいので、Perlの「Calendar」モジュールで何とかならないかと思い調べてみました。

Calendarモジュールで曜日を判定

どうやら、Calendarモジュールには「weekday」メソッドが用意されており、これで簡単に曜日を判定できるみたいです。Excelの「weekday」関数と同じような働きをするようで、曜日に合わせて下記の数字を返してくれます。

曜日 weekdayの戻り値
日曜日 0
月曜日 1
火曜日 2
水曜日 3
木曜日 4
金曜日 5
土曜日 6

日曜日が「7」になるようモードを切り替えられるといいのですが、そこまではできなさそうですね。Excelの「weekday」関数もデフォルトは日曜が「0」となっていました。なんで日曜からスタートなんでしょう。
まあいいかそれは。weekdayメソッドの戻り値が、1〜5の間であれば、その日は営業日というわけですね。

#!/usr/bin/env perl
use v5.16;
use warnings;
use utf8;
use open IO => qw/:utf8 :std/;
use autodie;

use Calendar;
my $date = Calendar->today;

for my $i (1 .. 7)
{
  printf "今日は %s %-10s: 働け!\n", $date->convert_to_Gregorian,
      $date->weekday_name if $date->weekday =~ m/[1-5]/;
  $date++;
}

結果はこんなかんじに。

今日は 09/03/2012 Monday    : 働け!
今日は 09/04/2012 Tuesday   : 働け!
今日は 09/05/2012 Wednesday : 働け!
今日は 09/06/2012 Thursday  : 働け!
今日は 09/07/2012 Friday    : 働け!

働きたくないですね(T T)
Calendarモジュールはインクリメントするだけで日付を1日進めてくれます。ほんと楽で助かります。

祝日はどうするって?

Calendarモジュールは、曜日の判定はできますが、祝日の判定はできません。祝日を営業日換算してスケジュールとか出してると、あとでいろんな人から突き上げを食らいそうです。

日本の祝日を判定するには、Calendar::Japanese::Holidayが便利です。日付を「isHoliday」関数に渡すと、祝日の場合は祝日名を返します。祝日でない場合はundefを返すので、祝日の判定としても使えます。ありがたいことに、振替休日の判定までしてくれます。

#!/usr/bin/env perl
use v5.16;
use warnings;
use utf8;
use open IO => qw/:utf8 :std/;
use autodie;

use Calendar;
use Calendar::Japanese::Holiday;

my $date = Calendar->new_from_Gregorian(5, 3, 2012);
my $furikae = 1;

for my $i (1 .. 7)
{
  if ($date->weekday =~ m/[1-5]/)
  {
    my $holiday = isHoliday($date->year, $date->month, $date->day, $furikae);
    unless ($holiday)
    {
      printf "今日は %s %-10s: 働け!\n", $date, $date->weekday_name;
    }
    else
    {
      printf "今日は %s %-10s: 祝日! %s\n", $date, $date->weekday_name, $holiday;
    }
  }
  $date++;
}

今度は祝日の多い5月前半あたりで試してみました。結果は下記のような感じになります。
これで祝日に働くなんて恐ろしいこともなくなりそうです。

今日は 05/03/2012 Thursday  : 祝日! 憲法記念日
今日は 05/04/2012 Friday    : 祝日! みどりの日
今日は 05/07/2012 Monday    : 働け!
今日は 05/08/2012 Tuesday   : 働け!
今日は 05/09/2012 Wednesday : 働け!

どうやって営業日数を計算するか

土日祝の判定はできるようになったので、あとはどうやって営業日を計算するかを考えるだけです。今回はwhileループで、下記の3つの条件を満たすときだけ、カウンター変数をインクリメントする方式で、営業日数を数え上げるようにしました。

  • Calendarモジュールのweekdayメソッドで、1〜5を返す(月〜金と判定)
  • Calendar::Japanese::HolidayモジュールのisHolidayの戻り値がundefとなる(祝日ではないと判定)
  • 年末年始ではない
#!/usr/bin/env perl
use v5.16;
use warnings;
use utf8;
use open IO => qw/:utf8 :std/;
use autodie;

use Calendar;
use Calendar::Japanese::Holiday;

my $epoc = Calendar->new_from_Gregorian(10, 22, 2012);
my $date = $epoc;

# 年末年始や夏期休暇はここに入れる
my @kyuka_list = qw(
  12/29/2012
  12/30/2012
  12/31/2012
  01/01/2013
  01/02/2013
  01/04/2013
  01/05/2013
  01/06/2013
);

sub calc_date
{
  my ($eigyoubi, $milestone) = @_;
  my $counter = 0;
  while ($counter < $eigyoubi)
  {
    if ($date->weekday =~ m/[1-5]/)
    {
      unless (isHoliday($date->year, $date->month, $date->day, 1))
      {
          $counter++ unless sprintf('%s', $date) ~~ @kyuka_list;
      }
    }
    $date++;
  }
  $date++ while $date->weekday =~ m/0|6/;
  $date++ while isHoliday($date->year, $date->month, $date->day, 1);
  printf "%s: %s => %s\n", $milestone, $date, $date->weekday_name;
}

printf "%s: %s => %s\n", '作業開始日', $epoc, $epoc->weekday_name;

# 原稿提出日を計算。執筆期間は30営業日
my $eigyoubi = 30;
calc_date($eigyoubi, '原稿提出日');

# 初校提出日を計算。製作期間は40営業日
$eigyoubi = 40;
calc_date($eigyoubi, '初校提出日');

結果は下記のような感じです。

作業開始日: 10/22/2012 => Monday
原稿提出日: 12/04/2012 => Tuesday
初校提出日: 02/06/2013 => Wednesday

カウントアップの条件を満たしたあとの日付が土日に当たる場合は、さらにインクリメントして平日になるよう調整しています。そのインクリメント先の日が祝日or振替休日だったら……とりあえずさらにインクリメントするようにしました。

年末年始の休暇判定がダサすぎるのでなんとかしたいところですが、あまりよい方法が思いつかないですね……。ここがなんとかなれば、マイナスの日を入れて引き算する、開始日と終了日から営業日数を求めるとか付け加えてモジュール化してもいいかもしれません。

まあ用事は済んだので、今日のところはとりあえずこれでよしとします。