2010年4月8日木曜日

Windows Power Shellで、時間の計算を便利にしてみます。



今回の記事では、対話型であるWindows Power Shellの特性を活かした超高機能電卓化計画の一環として、時間の計算を便利にしてみようと思います。

肝となるのは、TimeSpan型なのですが、この型には大きな制約があります。
たとえば、100時間とか入力したくとも、一度に入力できるのは、23時間59分までなんです。
100時間を表現するには、時間を日に繰り上げて、4日と4時間を意味する"4.04:00"に変換しないと受け付けてくれません。
逆に言えば、この変換さえ自動でやるコマンド(関数)を自分で作ってしまえば、まるで電卓のように時間の計算が楽々できてしまうわけです。

そこで、長い名前だと大変なので、Tというコマンド(関数)を作ってみました
詳しくはスクリーンショットにあるのですが、時間の計算が電卓感覚で簡単に出来ます

100時間は、単に(T "100:00")と入力するだけ。
内部的に、TimeSpan型の4日と4時間に変換します。

結果が、リストのようにズラッと出てきますが、これは結果が文字列じゃなくてTimeSpan型だからです。
TimeSpan型だからこそ、(T "100:00") + (T "200:30")という具合に、どんどん計算が出来ます
文字列で結果が欲しい場合は、計算結果の外側を括弧でくくって、.ToString()を付けて下さい。
((T "100:00") + (T "200:30")).ToString(); という形ですね。


入力する書式は、"最大4桁の日.最大4桁の時間:最大4桁の分"もしくは、"最大4桁の時間:最大4桁の分"のどちらかを守れば、自由自在。

たとえば、0時間365分という入力も、(T "00:360")で可能。

同じ時間を繰り返し入力するのが面倒な場合は、倍率パラメータで一気に求められます。
たとえば、8時間30分を20回加算したいという時は、(T "8:30" 20)だけでOK
一気に、7日と2時間(170時間)を算出します。

一度TimeSpan型で結果を求めてしまうと、後はこっちのもの。
TimeSpan型は、プロパティを参照することで、

Days = 日の切り出し
Hours=時間の切り出し
Minutes=分の切り出し
TotalDays=全てを日数に換算した値
TotalHours=全てを時間に換算した値
TotalMinutes=全てを分に換算した値
など(もっとありますが)を取得する事ができるので、どのようにも結果を利用する事が出来ます。

たとえば、8時間30分を15回分に4時間を10回分加算。そこから45分を20回減算し、全てを時間で求めたい場合は・・・
((T "08:30" 15) + (T "04:00" 10) - (T "00:45" 20)).TotalHours;
だけでOK。

一旦、変数に格納するのが便利。
$obj_time = ((T "08:30" 15) + (T "04:00" 10) - (T "00:45" 20));
としておけば、

$obj_time.TotalHours; で全てを時間に換算した値が得られるし、結果が$obj_timeという変数に詰まっているので、再利用がいとも簡単です。

■プログラムソース(インデントが消えていますがご容赦下さい)■

#--------------------------------------
#時間の計算
#(パラメータの説明)
# $p_value : 時間文字列(日.時:分)形式
# $p_magnification : 倍率(指定しなければ1)
#--------------------------------------
function global:T(
[string]$p_value = "",
[Int32]$p_magnification = 1
)
{
#----------------------------
#変数定義
#----------------------------
#チェック用書式1
[Regex]$obj_rx1 = $(New-Object Regex("^\d{1,4}\.\d{1,4}:\d{1,4}$"));
#チェック用書式2
[Regex]$obj_rx2 = $(New-Object Regex("^\d{1,4}:\d{1,4}$"));
#インデックス
[Int32]$i = 0;
#基礎結果エリア
[TimeSpan]$obj_base = $(New-Object TimeSpan(0,0,0,0));
#結果エリア
[TimeSpan]$obj_result = $(New-Object TimeSpan(0,0,0,0));
#日時取得配列
[string[]]$strArr_ts = $(New-Object string[] 3);
#日時計算配列
[Int32[]]$intArr_ts = $(New-Object Int32[] 3);
#日時分割用キャラクター配列
[char[]]$charArr_c = $(New-Object char[] 2);

#----------------------------
#処理
#----------------------------
try
{
#入力パラメータチェック
if(!($obj_rx1.IsMatch($p_value)))
{ #書式1にミスマッチ
if($obj_rx2.IsMatch($p_value))
{ #書式2にマッチした場合は日を補完して処理する
$p_value = "0." + $p_value;
}
else
{
throw ("入力パラメータ(" + $p_value + ")は書式エラーです。");
}
}

#日時配列取得
$charArr_c[0] = ".";
$charArr_c[1] = ":";
$strArr_ts = $p_value.Split($charArr_c);

#日時計算配列セット
#@@@@@@@@@@[LOOP-START]@@@@@@@@@@
for($i=0;$i -lt $strArr_ts.Length;$i++)
{
$intArr_ts[$i] = [Int32]::Parse($strArr_ts[$i]);
}
#@@@@@@@@@@[LOOP-END ]@@@@@@@@@@

#日時調整計算処理
#分の時間への繰上げ計算
$intArr_ts[1] = $intArr_ts[1] +
[Math]::Floor([double]$intArr_ts[2] / [double]60);
#分の調整計算
$intArr_ts[2] = $intArr_ts[2] -
[Math]::Floor([double]$intArr_ts[2] / [double]60) * 60;
#時間の日への繰上げ処理
$intArr_ts[0] = $intArr_ts[0] +
[Math]::Floor([double]$intArr_ts[1] / [double]24);
#時間の調整計算
$intArr_ts[1] = $intArr_ts[1] -
[Math]::Floor([double]$intArr_ts[1] / [double]24) * 24;

#基礎結果のセット
$obj_base = $obj_base.Add(
$(New-Object TimeSpan($intArr_ts[0],$intArr_ts[1],$intArr_ts[2],0))
);

#結果の算出(倍率処理)
#@@@@@@@@@@[LOOP-START]@@@@@@@@@@
for($i=0;$i -lt $p_magnification; $i++)
{
$obj_result = $obj_result.Add($obj_base);
}
#@@@@@@@@@@[LOOP-END ]@@@@@@@@@@

}
catch [Exception]
{ #例外が発生したらシェルにぶつけて強制終了
throw $Error[0];
}
return $obj_result;

}