C#プログラミングTips
日付と時刻の処理を知る
デジタルアドバンテージ
2001/10/05
|
|
今回は、日付や時刻に関連するクラスを取り上げて解説する。単に日付や日時を取得するだけでなく、.NET Frameworkには、それらを処理するための便利なクラスが多数実装されている。
現在の日付および時刻の表示
まず最も簡単なものから始めよう。次のサンプル・プログラムは、現在の日付と時刻を取得し、いくつかのフォーマットで表示する。メッセージを表示するための各Console.WriteLineメソッド呼び出しの直下または右横に、プログラムの出力結果(の例)をコメントとして記述している。
1: // datetime1.cs
2:
3: using System;
4:
5: class datetime1 {
6: public static void Main() {
7:
8: Console.WriteLine(DateTime.Now);
9: // 2001/10/04 7:11:41
10: Console.WriteLine(DateTime.Today);
11: // 2001/10/04 0:00:00
12:
13: DateTime dt = DateTime.Now;
14: Console.WriteLine(dt.Year + "年"); // 2001年
15: Console.WriteLine(dt.Month + "月"); // 10月
16: Console.WriteLine(dt.Day + "日"); // 4日
17: Console.WriteLine(dt.Hour + "時"); // 7時
18: Console.WriteLine(dt.Minute + "分"); // 11分
19: Console.WriteLine(dt.Second + "秒"); // 41秒
20:
21: Console.WriteLine(dt.ToString("F"));
22: // 2001年10月4日 7:11:41
23: Console.WriteLine(dt.ToString("yyyy年MM月dd日 HH時mm分ss秒"));
24: // 2001年10月04日 07時11分41秒
25: }
26: }
|
|
現在の日付および時刻を表示するサンプル・プログラム |
|
日付や時刻を扱ういくつかのクラスの中で、主役となるのは「DateTime構造体」である(これはクラスではなく、「構造体:Structure」である)。現在の日付および時刻を取得するためには、そのスタティックなNowメソッドを使用する。また、現在の日付だけがほしい場合には、Todayメソッドを使用する。これらのメソッドは、DateTime型のオブジェクトを返す。DateTimeはその値を文字列に変換するToStringメソッドを持つため、次のようにして、そのまま内容を表示することができる。
8: Console.WriteLine(DateTime.Now);
9: // 2001/10/04 7:11:41
10: Console.WriteLine(DateTime.Today);
11: // 2001/10/04 0:00:00
|
|
現在の時刻と日付を表示する |
プログラムによっては、これ以外の書式で日時を表示したい場合もあるだろう。日付や時間の各値を個別に取得するためのプロパティも、もちろん用意されている(14〜19行目)。また、次のようにいくつかの標準的な書式や、独自の書式を設定して表示することも可能だ。
21: Console.WriteLine(dt.ToString("F"));
22: // 2001年10月4日 7:11:41
23: Console.WriteLine(dt.ToString("yyyy年MM月dd日 HH時mm分ss秒"));
24: // 2001年10月04日 07時11分41秒
|
|
書式を指定して日時を表示する |
この場合には、ToStringメソッドの引数として渡す文字列に、書式を示す特定の文字を指定する。例えば21行目の“F”は、最も長い標準的な書式で日時情報を文字列に変換する。一方、23行目では、独自の書式を指定しており、“yyyy”は年を4桁で、“MM”は月を「0埋め(1桁の値の場合には、『0』を表記する)かつ2桁」で変換するといった具合だ。これら書式の一覧はリファレンス・マニュアルのDateTimeFormatInfoクラスの項に列挙されている。
|
DateTimeFormatInfoクラスの解説 |
DateTime型の値を書式付きで文字列に変換する際の書式指定文字が列挙されている。
|
特定の日時と日時の演算
DateTime構造体のインスタンスを自分で“new”すれば、現在時刻とは無関係なDateTime型のオブジェクトを生成することができる。次のサンプル・プログラムは、とある日(ちなみに筆者の誕生日)のインスタンスを作成し、まずその日の曜日を表示する。続いてその日から現在までの経過日数を表示する。
1: // datetime2.cs
2:
3: using System;
4:
5: class datetime2 {
6: public static void Main() {
7:
8: DateTime dt = new DateTime(1968, 6, 7);
9: Console.WriteLine(dt.DayOfWeek);
10: // Friday
11:
12: dt = DateTime.Parse("1968/6/7");
13: TimeSpan ts = DateTime.Now - dt;
14: Console.WriteLine(ts);
15: // 12172.07:11:41.7486256
16: Console.WriteLine(ts.Days);
17: // 12172
18: Console.WriteLine(ts.TotalDays);
19: // 12172.2997887572
20: }
21: }
|
|
特定の日の曜日と経過日数を表示するサンプル・プログラム |
|
8行目では、DateTime構造体の7つあるコンストラクタのうち、引数に年、月、日の3つをとるものを使用してインスタンスを作成している。そして続く9行目では、プロパティDayOfWeekによりその日の曜日を取得している(そう確か金曜日だったはずだ)。
8: DateTime dt = new DateTime(1968, 6, 7);
9: Console.WriteLine(dt.DayOfWeek);
10: // Friday
|
|
特定の日付でDateTimeオブジェクトを作成し、その曜日を表示する |
プログラムの12行目では、Parseメソッドを用いた、DateTimeオブジェクトを作成するための別の方法を示している。このメソッドは、引数で指定された文字列を解析してDateTimeオブジェクトを作り出す。その文字列の書式は、先ほどのDateTimeFormatInfoクラスの項で列挙されている標準的な書式のいずれかに従っている必要がある。今回は省略しているが、書式が誤っている場合には例外が発生するため、本来なら例外処理が必須となる。
12: dt = DateTime.Parse("1968/6/7");
13: TimeSpan ts = DateTime.Now - dt;
14: Console.WriteLine(ts);
15: // 12172.07:11:41.7486256
16: Console.WriteLine(ts.Days);
17: // 12172
18: Console.WriteLine(ts.TotalDays);
19: // 12172.2997887572
|
|
文字列から日付を解析し、現在の日付までの経過日数を表示する |
13行目は、DateTime型同士の引き算を行っており、この演算の結果はTimeSpan型となる。このTimeSpan型は期間を表すための構造体だ。つまり時刻ではなく時間である。この結果を14行目で表示していて、その値は“12172.07:11:41.7486256”となっているが、これは「1万2172日と7時間11分41.7486256秒」を意味する。12行目では時刻を指定しなかったため、1968/6/7の午前0:00:00を基準に計算されている(ちなみに現在の時刻は午前7:11:41ごろ)。
TimeSpan型のオブジェクトから、例えば日数だけを取り出す場合には、16行目のようにDaysプロパティを使用する。また結果を日数のみで取得したければ、18行目のようにDaysではなくTotalDaysプロパティを使用する。いずれにしろ現時点で筆者は生後1万2172日目というわけだ。
実行時間の計測
実験用のプログラムなどでよく使われる日時に関する処理に、コードの実行時間の計測がある。最後にこの方法を示しておこう。
次のプログラムでは、目的は同じだが、使用するクラスが異なる2つのループにかかった時間をそれぞれ秒単位で表示する。経過時間を秒単位で示すTimeSpan構造体のプロパティは“TotalSeconds”である。
1: // datetime3.cs
2:
3: using System;
4: using System.Text;
5:
6: class datetime3 {
7: public static void Main() {
8:
9: DateTime start, end;
10: string str;
11:
12: // stringによるループ
13: //
14: start = DateTime.Now;
15: str = "";
16: for (int i = 0; i < 50000; i++) {
17: str += "a";
18: }
19: end = DateTime.Now;
20:
21: Console.WriteLine(str.Length);
22: // 50000
23: Console.WriteLine((end - start).TotalSeconds);
24: // 4.2462328
25:
26: // StringBuilderによるループ
27: //
28: start = DateTime.Now;
29: StringBuilder sb = new StringBuilder();
30: for (int i = 0; i < 50000; i++) {
31: sb.Append("a");
32: }
33: str = sb.ToString();
34: end = DateTime.Now;
35:
36: Console.WriteLine(str.Length);
37: // 50000
38: Console.WriteLine((end - start).TotalSeconds);
39: // 0.0100147
40: }
41: }
|
|
処理にかかる時間を計測するサンプル・プログラム |
|
余談となるが、計測しているループの中身についても少し説明しておこう。2つあるループは、どちらも空の文字列に1文字ずつ加えていき、それを50000回繰り返すものだ。最終的にどちらも50000個の“a”を含んだ文字列が出来上がる。
1つ目のループでは、次のように、文字列に対して「+=演算子」を使用している。
16: for (int i = 0; i < 50000; i++) {
17: str += "a";
18: }
|
|
+=演算子を使用した文字列操作 |
2つ目のループでは、文字列の作成と操作を行うためのStringBuilderクラス(System.Text.StringBuilder)を使用して文字列を長くしていく。実行結果をみると分かるように、同じような処理を行っているにもかかわらず、双方のループでは実行時間に数百倍もの差が現れる(プログラムの実行にはPentium 4-1.7GHzのマシンを使用した)。
30: for (int i = 0; i < 50000; i++) {
31: sb.Append("a");
32: }
|
|
StringBuilderクラスを使用した文字列操作 |
文字列同士の“+”や“+=”による演算は便利であり、コードの見た目にもすっきりするのでつい使ってしまうことがあるが、これらの操作は、内部的にはこれまでの文字列オブジェクトを捨て、新しくオブジェクトを生成しているだけなのだ。C#において、文字列(System.String型あるいはstring型のオブジェクト)は、その値を変更することはできないのである。
これに対してStringBuilderクラスを使用すると、新しいオブジェクトを生成することなしに文字列を操作することができる(マニュアルにそう記されているし、実行結果もそれを裏付けている)。こちらのループは一瞬で終了する。いかに文字列に対する「+演算」が遅いかが分かる。これをループ内で使用している場合には注意が必要だ。