Perl製自作便利ツール紹介: 日付電卓

自分用に作った小っちゃいPerlツールの紹介シリーズです。

前回はこちら。

Perl製自作便利ツール紹介: 重複行抽出スクリプト - the code to rock

上記の「重複(&非重複)抽出ツール」は、主に日常用途を想定して作ったものですが、今回は逆に(というか)、仕事で使う必要に迫られて作ったものです。

具体的には、日にちや時間を加減する電卓です。

動機

電卓といえば計算機、計算機といえばコンピュータの起源とも言える何かですから、時間や日にちを計算するツールなんて絶対ネットのそこいら中にあるだろう、と想像されるわけですが、探してみると案外(数年前からその必要に迫られるごとに検索していますが)、めぼしいものが見つかりません。

そういうものがまったく無いわけではなくて、WebアプリでもiPhoneアプリでも、各所で作成&公開されている物があるのはあるのですが、いわばデファクト・スタンダードというのか、「このニーズにはとりあえずこれでしょ」と言える定番が無いというか。

たとえば、ソーシャルブックマークならはてブでしょ、とか(少なくとも日本では)、入門者のプログラミング系Q&AならStack Overflowでしょ、とか。料理レシピならCOOKPADでしょ、とか。検索なら・・もういいですか。

ようは、これだけニーズがありそうな分野であるにもかかわらず、どうして「鉄板」と言えるものがないのかなあ、というのは僕にとっていまだに大きな謎です。
(もしかしたら通常の電卓にデフォルトで仕込まれているそういう機能を僕が知らないだけ、とか・・?)

上述のWebアプリなどももちろん、以前に試せるかぎりは試したのですが、どうにもまどろっこしいというか・・。ネットに繋がっていないと使えなかったり、単純な目的に比して手間がかかったりして(入力や選択の対象が多いなど)、もっと少機能で軽快に動くものがほしい、というニーズが固まってきたため、もう他人様が作ってくれたものにどうこう言うのではなく、自分で自分の用途に一番最適化したそれを作ってみよう、という流れになったのでした。

あとはまあ、このぐらいの内容なら、今までに教わったことを元に出来ないワケがないだろう・・というか出来てみたい、いや出来てください、という欲求が高まったということでもあるのですが。

前置きが長くなりましたが、そういうモチベーションから作成したスクリプトを3本ご紹介します。

日付電卓

まずは「日付電卓」です。これが3本中の2本です。

なぜ2本もあるのかというと、一つは「1つの日付に1つの数値(日数)を加減する」もので、もう一つは「2つの日時の差を求める」というものだからです。

具体的には、前者がこちら。(date_num_calc.pl)

#!/usr/bin/env perl
use strict;
use warnings;
use feature 'say';
use Time::Piece;
use Time::Seconds;

my @list = <DATA>;
chomp @list;
for (@list) {
    $_ =~ s/\//-/g;
}
my $date = $list[0];
my $num = $list[1];

my $stamp_local = localtime->strptime("$date", '%Y-%m-%d');
my $add = $stamp_local + ONE_DAY * $num;
say $add->ymd." (add)";
my $substract = $stamp_local - ONE_DAY * $num;
say $substract->ymd." (substract)";

__DATA__

後者がこちらです。(datetime_substract.pl)

#!/usr/bin/env perl
use strict;
use warnings;
use feature 'say';
use Time::Piece;
use Time::Seconds;

my ($pday, $phour, $pminute, $psecond);
my ($day, $hour, $minute, $second) = (0, 0, 0, 0);

my @list = <DATA>;
chomp @list;
for (@list) {
    $_ =~ s/\//-/g;
    if ($_ =~ /^(\d\d?:.+)$/) {
        my $d = localtime->date;
        $_ = "$d $1";
    }
}

my $date1 = $list[0];
my $date2 = $list[1];
stamp($date1, $date2);

sub stamp {
    my ($date3, $date4) = @_;
    my $stamp_local = localtime->strptime("$date3", '%Y-%m-%d %T');
    my $stamp_local2 = localtime->strptime("$date4", '%Y-%m-%d %T');

    my $substract_day = ($stamp_local - $stamp_local2) / ONE_DAY;
    $day = sprintf("%d", $substract_day);
    my $substract_hour = (($stamp_local - $stamp_local2) - $day * ONE_DAY) / ONE_HOUR;
    $hour = sprintf("%d", $substract_hour);
    my $substract_minute = ($stamp_local - $stamp_local2 - ($day * ONE_DAY) - ($hour * ONE_HOUR)) / ONE_MINUTE;
    $minute = sprintf("%d", $substract_minute);
    my $substract_second = ($stamp_local - $stamp_local2 - ($day * ONE_DAY) - ($hour * ONE_HOUR) -  ($minute * ONE_MINUTE));
    $second = sprintf("%d", $substract_second);

    ($day, $hour, $minute, $second) = map {abs($_)} ($day, $hour, $minute, $second);
    plural($pday, $phour, $pminute, $psecond);

    my $result = "$day $pday $hour $phour $minute $pminute $second $psecond\n";
    say $result;
}

sub plural {
    ($pday, $phour, $pminute, $psecond) = @_;
    unless ($day == 1) { $pday = 'days'; } else { $pday = 'day'; }
    unless ($hour == 1) { $phour = 'hours'; } else { $phour = 'hour'; }
    unless ($minute == 1) { $pminute = 'minutes'; } else { $pminute = 'minute'; }
    unless ($second == 1) { $psecond = 'seconds'; } else { $psecond = 'second'; }
}
__DATA__

いずれも、ファイル下方の「__DATA__」の下に、1行につき一つ、対象となる日にちや数値を入れて実行する次第です。

たとえば、前者の「__DATA__」以下をこのようにすると、

__DATA__
2016-01-27
98

このように出力されます。

2016-05-04 (add)
2015-10-21 (substract)

「2016-01-27」に98日分足した日にちと、98日分引いた日にちが出てきます。

また、後者の方をこのようにしておくと、

__DATA__
2015-02-16 02:05:28
2015-01-19 00:18:34

このように差が出力されます。

28 days 1 hour 46 minutes 54 seconds

この際、時刻を入れずに設定すると自動的に0時0分0秒となり、また日付を入れずに設定すると実行した日を日付に入れるので、たとえば2015/2/16に以下のように実行したら、

__DATA__
2015-02-16
00:18:34

こんな感じになります。

0 days 0 hours 18 minutes 34 seconds

この際、差は絶対値で出しています。

最初は絶対値にしていなかったので、上記の例だと

0 days 0 hours -18 minutes -34 seconds

みたいになってしまい、見づらかったのでそのように直しました。

さらにちなみに、後者は初めは日数だけの差分を求めるようにしていたので、もっとシンプルな内容(行数が少ない)だったのですが、何となく時間も一緒に計算できるようにしたくなってしまったので、そうしたらえらい長くなりました。リファクタリング待ちですね。

謝辞

以前はこれらをDatetimeモジュールで作っていたのですが、それは少し前にコアモジュールから外れたという話を聞いたので、Time::Piece と Time::Seconds を使ったコードに頑張って置き換えました。

その際には、言わずと知れたこちらのモジュールガイド本と、

Perl CPANモジュールガイド

Perl CPANモジュールガイド

毎度お世話になっています木本裕紀さんのブログ、

その他、検索先の皆様に助けて頂きました。ありがとうございました。

用途

このスクリプトは、とくにスケジューリング作業のときによく使います。

前者の「日付 + 日数」電卓なら、「このタスクは20日はかかるなあ・・取り掛かれるのは多分2週間後の水曜ぐらいだから、その日から数えて終わるのは何月何日になるだろう?」なんて思った時に使います。
まあ、そういう対象が1個や2個ならばカレンダーを見ながら(もちろんエディタでcalendar.vimを見ながら)目視&暗算すれば良さそうなものですが、スケジューリングのときには何十という対象についてそういう算出をするので、こういうツールがあった方がラクだなと。

また、後者の「日時 - 日時」電卓であれば、「以前にやってA日からB日までかかった作業があるけど、結局その作業は何日間で終わったんだろう? また同じ作業をするから日数の見積もりの目安にしよう」なんていう時に、BからAを引いて欲しい数字を求めます。

というか、そういう時、他の方はどうやって乗り切っているのでしょうね・・? Excelですかね? まあ、たしかにExcelならできるでしょうけど、でも皆が皆Excelでそれを求めているとは(とくにプログラマーの人は)思えないのですが・・

長くなったので本日はここまでにします。続きは次回!