前回の時間計算機をつくるの続編です。前回は時刻の計算をしました。今回は時間の計算です。3分40秒に100秒を足して3で割ったら何分何秒みたいな計算ができるようにします。それから見た目を電卓に近づけます。そして時間の単位を秒だけとか分だけで表示できるようにします。
Contents
仕様
数字のキーをおすと普通の電卓のように数字が表示されます。そのあと「時間」「分」「秒」を指定します。1時間100分30秒とか330秒1時間95分のようなおかしな入力をしても適切な表現(両方とも2時間40分30秒)に変換して計算できるようにします。
30分に10分を足したり引いたりすることはできますが、30分に10を足すことはできません。また30分に10を掛けたり割ったりすることはできますが、30分に10分を掛けたり割ったりすることはできません。また0で割ることはできません。そのため入力された計算式が正しいかどうかもチェックしなければなりません。
数字のキーと「時間」「分」「秒」をクリックすることで時間を表わす文字列ができます。また「時間」「分」「秒」の前後には半角スペースをつけます。これによって文字列の最後が半角スペースなのに「時間」「分」「秒」を入力するのは不正な入力と判断して入力として受け付けないことができます。
計算をするときにはこの文字列をTimeSpan構造体に変換します。ではどうやって時間を表わす文字列をTimeSpan構造体に変換すればよいのでしょうか?
まず半角スペースを取り除きます。そしてこれを文字列 “時間”, “分”, “秒”で分割します。もし分割の結果、空文字列ができた場合は取り除きます。これで値を取得することができます。また同じ文字列を”0”, “1”, “2”, “3”, “4”, “5”, “6”, “7”, “8”, “9”で分割します。この場合も空文字列ができた場合は取り除きます。
これで二つの配列が生成され、一方が整数文字列の配列となり、もう片方が時間の単位を示す文字列の配列となります。そして両者の要素数は同じになっているはずです。”2時間40分30″のような文字列(整数文字列の配列と時間の単位を示す文字列の配列の要素数が一致していない)は不正な入力として処理の対象からはずします。それから”2時間分30秒”のような文字列は「時間」「分」「秒」のボタンがクリックされたときに行なわれるチェックによって生成されないはずです。
文字列をTimeSpan構造体に変換できたらこれをフィールド変数に保存して、つぎに生成された時間を示す文字列をTimeSpan構造体に変換したもの、または数値を示す文字列をint型に変換したものと加減乗除の演算をおこないます。このとき足し算と引き算はTimeSpan構造体同士、かけ算と割り算はTimeSpan構造体とint型のあいだでしか演算できないので計算前にチェックが必要です。
Operator列挙体
デザイナで以下のようにLabel、Button、RadioButtonを配置します。
最初にOperator列挙体を定義します。これは現在入力されている文字列はそれまでの計算結果に対して何をしようとしているかを示すものです。これまでの計算結果との関係なので最初に入力されたものは未定です。
1 2 3 4 5 6 7 8 9 |
// やろうとしている演算は enum Operator { None, // 未定 Plus, // 足し算 Minus, // 引き算 X, // かけ算 Div, // 割り算 } |
Form1クラス
Form1クラスですが、フィールド変数が3つあります。
どのような演算がしようとしているかを次の文字列が確定するまで記憶しておかなければなりません。そのために存在するのが_operatorです。現在入力されている時間を示す文字列は_inputに保存しておきます。これまでの演算結果は_totalに保存しておきます。
これらはClearボタンやAllClearボタンをクリックしたときに適切にクリアされなければなりません。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
public partial class Form1 : Form { Operator _operator = Operator.None; TimeSpan _total = new TimeSpan(); string _input = ""; public Form1() { InitializeComponent(); RadioButtonShowNormal.Checked = true; // 計算結果をどのような形式で表示するか(最初は普通の形式) label1.Text = ""; // ここに入力された文字列が表示される label2.Text = ""; // ここにこれまでの計算結果が表示される } } |
数字のボタンがクリックされたとき
数字のボタンがクリックされたときの処理を示します。_input にその文字を追加してそれをlabel1に表示させるだけです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
public partial class Form1 : Form { private void Button1_Click(object sender, EventArgs e) { _input += "1"; label1.Text = _input; } private void Button2_Click(object sender, EventArgs e) { _input += "2"; label1.Text = _input; } private void Button3_Click(object sender, EventArgs e) { _input += "3"; label1.Text = _input; } private void Button4_Click(object sender, EventArgs e) { _input += "4"; label1.Text = _input; } private void Button5_Click(object sender, EventArgs e) { _input += "5"; label1.Text = _input; } private void Button6_Click(object sender, EventArgs e) { _input += "6"; label1.Text = _input; } private void Button7_Click(object sender, EventArgs e) { _input += "7"; label1.Text = _input; } private void Button8_Click(object sender, EventArgs e) { _input += "8"; label1.Text = _input; } private void Button9_Click(object sender, EventArgs e) { _input += "9"; label1.Text = _input; } private void Button0_Click(object sender, EventArgs e) { _input += "0"; label1.Text = _input; } } |
「時間」「分」「秒」のボタンがクリックされたとき
「時間」「分」「秒」のボタンがクリックされたときの処理を示します。この場合も文字列を_inputに追加していくのですが、最後が半角スペースであったり_inputが空文字列の場合は不正な入力になるので、チェックして不正な入力がされた場合は処理はおこなわずその旨を表示します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
public partial class Form1 : Form { private void ButtonHour_Click(object sender, EventArgs e) { if (_input != "" && _input.Last() != ' ') { _input += " 時間 "; label1.Text = _input; } else label1.Text += " 不正な入力"; } private void ButtonMinute_Click(object sender, EventArgs e) { if (_input != "" && _input.Last() != ' ') { _input += " 分 "; label1.Text = _input; } else label1.Text += " 不正な入力"; } private void ButtonSecond_Click(object sender, EventArgs e) { if (_input != "" && _input.Last() != ' ') { _input += " 秒 "; label1.Text = _input; } else label1.Text += " 不正な入力"; } } |
文字列をTimeSpan構造体に変換する処理
文字列をTimeSpan構造体に変換する処理を示します。文字列のなかの半角スペースを除去して数値と単位の文字列でふたつの配列に分解します。不正な入力を排除しているのであればふたつの配列の要素数は同じはずなので、ここからTimeSpan構造体を生成することができます。
単位と数値のペアをつくって処理をするので”200分300秒1時間50秒”というメチャクチャな文字列でも普通の時間(この場合は4時間25分50秒)に変換することができます。もし文字列が時間を示す文字列になっていない場合(”1時間200″のように最後が単位で終わっていない場合や”10″など数値を示す文字列になっている場合)はnullを返します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
public partial class Form1 : Form { TimeSpan? GetTimeSpanFromInput() { string input = _input.Replace(" ", ""); string[] vs1 = { "時間", "分", "秒" }; string[] vs2 = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9" }; string[] values = input.Split(vs1, StringSplitOptions.RemoveEmptyEntries); string[] units = input.Split(vs2, StringSplitOptions.RemoveEmptyEntries); TimeSpan ts = new TimeSpan(); if (values.Length != units.Length) { return null; } for(int i=0; i<values.Length; i++) { int value = int.Parse(values[i]); if (units[i] == "時間") ts += new TimeSpan(0, 1 * value, 0, 0); else if (units[i] == "分") ts += new TimeSpan(0, 0, 1 * value, 0); else if (units[i] == "秒") ts += new TimeSpan(0, 0, 0, 1 * value); } return ts; } } |
計算結果を表示する
「+」「-」「×」「÷」のボタンがクリックされたときの処理を示します。
この場合は計算をおこないます。計算をおこない結果を表示するShowTotalメソッドは後述します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
public partial class Form1 : Form { private void ButtonPlus_Click(object sender, EventArgs e) { ShowTotal(); _operator = Operator.Plus; } private void ButtonMinus_Click(object sender, EventArgs e) { ShowTotal(); _operator = Operator.Minus; } private void ButtonX_Click(object sender, EventArgs e) { ShowTotal(); _operator = Operator.X; } private void ButtonDiv_Click(object sender, EventArgs e) { ShowTotal(); _operator = Operator.Div; } } |
計算をおこない結果を表示するShowTotalメソッドを示します。
_inputに格納されている文字列はTimeSpan構造体に変換できるものか、int型に変換できるものか、いずれにも該当しないものであることが想定されます。
_operatorを調べれば演算が加減乗除のどれか特定できます。足し算と引き算であればGetTimeSpanFromInputメソッドはnullを返すことがあってはならないし、かけ算割り算をするのであればint.Parse(_input);を実行したときに例外が発生してはいけません。
計算ができる場合は計算結果を_totalに格納するとともに計算結果を表示させます。そうでない場合は不正な演算がおこなわれようとしている旨を表示します。
計算結果は○年○ヵ月○日○時間○分○秒のような形で表示しますが、1分と2分の足し算をして0年0ヵ月0日0時間3分0秒と長々と表示されても見づらいだけなので短い時間の場合は0になる単位は省略して表示させます。また時間が0以下になる場合はマイナスの記号をつけて絶対値を表示します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
public partial class Form1 : Form { void ShowTotal() { int value = 1; bool isNumber = false; try { value = int.Parse(_input); isNumber = true; } catch { } TimeSpan? ts = GetTimeSpanFromInput(); if (_operator == Operator.None) { if (ts != null) _total = (TimeSpan)ts; else label1.Text = "不正な演算"; } if (_operator == Operator.Plus) { if(ts != null) _total += (TimeSpan)ts; else label1.Text = "不正な演算"; } if (_operator == Operator.Minus) { if (ts != null) _total -= (TimeSpan)ts; else label1.Text = "不正な演算"; } if (_operator == Operator.X) { if (isNumber) _total *= value; else label1.Text = "不正な演算"; } if (_operator == Operator.Div) { if (isNumber && value != 0) _total /= value; else label1.Text = "不正な演算"; } ShowTotal2(); _input = ""; } void ShowTotal2() { string sign = ""; if (_total < new TimeSpan()) sign = "- "; int years = _total.Days / 365; int months = (_total.Days % 365) / 30; int days = (_total.Days % 365) % 30; years = Math.Abs(years); months = Math.Abs(months); days = Math.Abs(days); int hours = Math.Abs(_total.Hours); int minutes = Math.Abs(_total.Minutes); int seconds = Math.Abs(_total.Seconds); if (years > 0) label2.Text = $"合計 {sign}{years} 年 {months} ヵ月 {days} 日 {hours} 時間 {minutes} 分 {seconds} 秒"; else if (months > 0) label2.Text = $"合計 {sign}{months} ヵ月 {days} 日 {hours} 時間 {minutes} 分 {seconds} 秒"; else if (days > 0) label2.Text = $"合計 {sign}{days} 日 {hours} 時間 {minutes} 分 {seconds} 秒"; else label2.Text = $"合計 {sign}{hours} 時間 {minutes} 分 {seconds} 秒"; } } |
「=」のボタンがクリックされたときの処理を示します。この場合はラジオボタンの状態をみて適切な形式で計算結果を表示します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
public partial class Form1 : Form { private void ButtonResult_Click(object sender, EventArgs e) { ShowTotal(); string sign = ""; if (_total < new TimeSpan()) sign = "- "; if (RadioButtonShowNormal.Checked) ShowTotal2(); if (RadioButtonShowDays.Checked) { double d = _total.TotalSeconds / 3600d / 24; label2.Text = $"合計 {sign}{Math.Abs(d)} 日"; } if (RadioButtonShowHours.Checked) { double d = _total.TotalSeconds / 3600d; label2.Text = $"合計 {sign}{Math.Abs(d)} 時間"; } if (RadioButtonShowMinutes.Checked) { double d = _total.TotalSeconds / 60d; label2.Text = $"合計 {sign}{Math.Abs(d)} 分"; } if (RadioButtonShowSeconds.Checked) { double d = _total.TotalSeconds; label2.Text = $"合計 {sign}{Math.Abs(d)} 秒"; } } } |
データのクリア
ClearボタンとAllClearボタンがクリックされたときの処理を示します。Clearボタンの場合は_inputがクリアされるだけですが、AllClearボタンの場合はそれだけでなく、_total_とoperatorもクリアされてしまいます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
public partial class Form1 : Form { private void ButtonClear_Click(object sender, EventArgs e) { _input = ""; label1.Text = _input; } private void ButtonAllClear_Click(object sender, EventArgs e) { _input = ""; label1.Text = _input; _total = new TimeSpan(); _operator = Operator.None; label2.Text = $"合計 {_total.Hours} 時間 {_total.Minutes} 分 {_total.Seconds} 秒"; } } |