ここでは.NET Frameworkにおける日付と時刻に関連するデータ型であるDateTime構造体・DateTimeOffset構造体・TimeSpan構造体と、それらの型を使った日付と時刻の操作について見ていきます。

なお、本文中にあるいくつかのサンプルコードについて、実行環境に設定されているタイムゾーン・言語・書式等によって実行結果・出力内容が変わるものもが存在します。 特に明記していない場合は、日本標準時(UTC+9)・日本語の環境での実行結果となります。

§1 日付・時刻・時間間隔と型

まずは.NET Frameworkで使用出来る日付と時刻を扱う型について大まかに見ていきます。

§1.1 日付と時刻

.NET Frameworkでは日時を表す値を扱うデータ型としてDateTime構造体およびがDateTimeOffset構造体が用意されています。 DateTime・DateTimeOffset構造体は特定の日付と時刻を表すだけでなく、日時の加減算日時同士の大小(前後)関係の比較を行うための機能、ローカル時刻と世界協定時刻(UTC)との変換を行うための機能などが用意されています。

§1.1.1 DateTime

DateTime構造体は、日付と時刻をひとまとめにして扱うデータ型ですが、単に日時の値を表すだけではなく、DateTimeでは時刻がローカル時刻とUTCのどちらかを表すのか、時刻の種類を明示することができるようになっていて、日時をローカル時刻もしくは世界協定時刻(UTC)として取り扱うことができるようになっています。

using System;

class Sample {
  static void Main()
  {
    // 2013年4月1日 午後3時0分30秒
    DateTime a = new DateTime(2013, 4, 1, 15, 0, 30);

    // 2013年4月1日 午後3時0分30秒 (ローカル時刻)
    DateTime b = new DateTime(2013, 4, 1, 15, 0, 30, DateTimeKind.Local);

    // 上の日時を世界協定時間に変換
    DateTime c = b.ToUniversalTime();

    // 2013年4月1日 午後3時0分30秒 (世界協定時刻・UTC)
    DateTime d = new DateTime(2013, 4, 1, 15, 0, 30, DateTimeKind.Utc);

    // 上の日時をローカル時刻に変換
    DateTime e = d.ToLocalTime();
  }
}

なお、この例で使用しているToLocalTimeメソッドToUniversalTimeメソッドは、DateTimeの時刻をローカル時刻・UTCの間で変換するためのメソッドです。 (ローカル時刻・UTCの変換については別途解説します)

§1.1.2 DateTimeOffset

.NET Frameworkでは、もうひとつ日時を表すデータ型としてDateTimeOffset構造体も用意されています。 これは.NET Framework 3.5から導入された構造体です。

DateTimeOffsetはDateTimeとよく似た構造体で、DateTimeにオフセット情報(UTCからの時差)を持たせたものに相当します。 つまり、DateTimeOffsetは日付と時刻に加え、オフセット情報をひとまとめにして扱います。 DateTimeにおける時刻の種類(Kindプロパティ)の代わりとして、DateTimeOffsetではオフセット情報(Offsetプロパティ)が存在すると言うこともできます。

これにより、DateTimeではローカルまたはUTCの時刻のみしか扱えないのに対して、DateTimeOffsetでは任意のタイムゾーンの日時が扱えるという違いがあります。

using System;

class Sample {
  static void Main()
  {
    // 2013年4月1日 午後3時0分30秒 (ローカル時刻)
    DateTime a = new DateTime(2013, 4, 1, 15, 0, 30);

    // 2013年4月1日 午後3時0分30秒 (UTC)
    DateTimeOffset b = new DateTimeOffset(2013, 4, 1, 15, 0, 30, TimeSpan.Zero);

    // 2013年4月1日 午後3時0分30秒 (UTC+9)
    DateTimeOffset c = new DateTimeOffset(2013, 4, 1, 15, 0, 30, new TimeSpan(9, 0, 0));

    // 2013年4月1日 午後3時0分30秒 (UTC-5)
    DateTimeOffset d = new DateTimeOffset(a, new TimeSpan(-5, 0, 0));
  }
}

DateTimeでは常にローカル時刻もしくはUTCのどちらかに変換して日時を格納するためオフセット値は消失しますが、DateTimeOffsetではオフセット値を個別に格納するようになっているため、オフセット情報を消失することなく日時を格納することが出来ます。

例えば、実行環境が日本標準時に設定されている環境において、東部標準時での時刻をDateTimeに格納しようとする場合を考えます。 この場合DateTimeでは、ローカル時刻である日本標準時か、もしくはUTCのどちらかに変換して格納しなければならないため、DateTimeに格納する際に「時刻は東部標準時のものである」という情報は失われますが、DateTimeOffsetを使えばオフセット値を維持したまま格納することができるため、「時刻は東部標準時である」という情報は維持されます。

using System;

class Sample {
  static void Main()
  {
    // ESTでの時刻を表す文字列
    string dtm = "2013-04-01T15:00:30-05:00";

    // 文字列からDateTime・DateTimeOffsetに変換
    DateTime a = DateTime.Parse(dtm);
    DateTimeOffset b = DateTimeOffset.Parse(dtm);

    Console.WriteLine(a);
    Console.WriteLine(b);
  }
}
実行結果
2013/04/02 5:00:30
2013/04/01 15:00:30 -05:00

ただ、DateTimeOffsetに格納されるのはオフセット値のみであり、具体的にどのタイムゾーンの日時かといった情報ではありません。 そのため、例えばUTC+9である日本標準時の日時と、同じくUTC+9である韓国標準時の日時はまったく同一の値として扱われる事になります。 タイムゾーンに関するより高度な操作が必要な場合はTimeZoneInfoクラスを使う必要があります。

§1.1.3 DateTimeとDateTimeOffsetの違い

オフセット情報を持たせられるかといった違いを除くと、DateTimeとDateTimeOffsetの両者にはほとんど同じメソッド・プロパティが用意されています。 従って、基本的にはDateTimeもDateTimeOffsetも同じように扱うことが出来ます。

扱う日時がローカル時刻のみ、もしくは単一のタイムゾーン内の時刻のみの場合であればDateTimeを使い、オフセット情報を維持する必要がある場合や、異なるタイムゾーンの時刻を扱う場合はDateTimeOffsetを選ぶ、といった使い分けをします。



§1.1.4 DateTime・DateTimeOffsetと暦法

DateTime・DateTimeOffsetでは、日時はグレゴリオ暦のものとして扱われます。 そのため、各月の日数うるう年などはグレゴリオ暦の暦法に従います。

DateTime・DateTimeOffsetはグレゴリオ暦で日時を扱いますが、.NET FrameworkではCalendarクラスを使うことでヘブライ暦・イスラム暦などの暦も扱えるようになっています。

§1.2 時間間隔 (TimeSpan)

.NET Frameworkでは、日時を表すDateTime・DateTimeOffsetと合わせて、時間間隔を表すTimeSpan構造体が用意されています。 TimeSpanは、例えば「36時間」や「50ミリ秒」といった時間の長さを一つの型で表すために使います。

時間の長さをint・doubleなどの数値型で表そうとした場合、それが秒を表すのか、分を表すのか、といった単位の扱いや変換が煩雑になりがちですが、TimeSpanを使えばそういった煩雑さは解消されます。 TimeSpanでは加減算もサポートされているので、互いに単位の異なる時間間隔の加減算も容易に行うことができます。

using System;

class Sample {
  static void Main()
  {
    // 3日と1時間30分0秒
    TimeSpan a = new TimeSpan(3, 1, 30, 00);

    // aを分単位で表すと何分になるか
    Console.WriteLine("{0} = {1}分", a, a.TotalMinutes);

    // 210分
    TimeSpan b = TimeSpan.FromMinutes(210.0);

    // bは何時間何分か
    Console.WriteLine("{0} = {1}時間{2}分", b, b.Hours, b.Minutes);

    // -1500ミリ秒
    TimeSpan c = TimeSpan.FromMilliseconds(-1500);

    // cを時間単位で表すと何時間になるか
    Console.WriteLine("{0} = {1}時間", c, c.TotalHours);

    // bとcの合計時間
    TimeSpan d = b + c;

    Console.WriteLine(d);
  }
}
実行結果
3.01:30:00 = 4410分
03:30:00 = 3時間30分
-00:00:01.5000000 = -0.000416666666666667時間
03:29:58.5000000

DateTime・DateTimeOffsetがある特定の時点を表すものであるのに対して、TimeSpanは二つの時刻間の差を表すものとも言えます。 実際、DateTime・DateTimeOffsetで日時の加減算を行う場合、TimeSpanを使うことが出来るようになっています。

§2 DateTime・DateTimeOffset

ここでは、DateTimeおよびDateTimeOffsetを使った日時の操作について見ていきます。 ほとんどの場合において、DateTimeとDateTimeOffsetは同様に扱うことができます。

§2.1 現在日時の取得

現在日時を取得するにはNowプロパティを参照します。 Nowプロパティで取得できる現在日時はローカル時刻となります。 UTCでの現在日時を取得したい場合は、UtcNowプロパティを参照します。

using System;

class Sample {
  static void Main()
  {
    // ローカル時刻での現在日時
    Console.WriteLine(DateTime.Now);
    Console.WriteLine(DateTimeOffset.Now);

    // UTCでの現在日時
    Console.WriteLine(DateTime.UtcNow);
    Console.WriteLine(DateTimeOffset.UtcNow);
  }
}
実行結果例
2013/04/01 15:00:30
2013/04/01 15:00:30 +09:00
2013/04/01 6:00:30
2013/04/01 6:00:30 +00:00

時刻が不要で、今日の日付のみを取得したい場合は、Todayプロパティを参照します。 Todayプロパティでは、時刻部分が 0時0分0秒(日付変更直後) の値が返されます。 また、Nowプロパティと同様返される日時はローカル時刻となります。

なお、DateTimeOffsetにはTodayプロパティは存在しません。 コンストラクタで日付のみを指定してインスタンスを作成する必要があります。 もしくは、DateTimeOffset.Nowプロパティで現在日時を取得した後、Dateプロパティで日付のみを取得すれば今日の日付が得られますが、Dateプロパティで得られる値の型はDateTimeとなります。

using System;

class Sample {
  static void Main()
  {
    // 今日の日付を取得する
    Console.WriteLine(DateTime.Now);
    Console.WriteLine(DateTime.Today);
    Console.WriteLine(DateTimeOffset.Now.Date);
    Console.WriteLine();

    // DateTimeOffsetで今日の日付を取得する
    DateTimeOffset today = new DateTimeOffset(DateTime.Today, DateTimeOffset.Now.Offset);

    Console.WriteLine(DateTimeOffset.Now);
    Console.WriteLine(today);
  }
}
実行結果例
2013/04/01 15:00:30
2013/04/01 0:00:00
2013/01/01 0:00:00

2013/04/01 15:00:30 +09:00
2013/04/01 0:00:00 +09:00

二つの時点でNowプロパティの値を取得することで、次の例のように処理の経過時間を計測することも出来ます。

Nowプロパティから取得した値を使って経過時間を計測する
using System;
using System.Threading;

class Sample {
  static void Main()
  {
    // 開始時刻を保持
    DateTime startTime = DateTime.Now;

    // 計測対象の処理と仮定
    Thread.Sleep(3000);

    // 終了時刻を保持
    DateTime endTime = DateTime.Now;

    // 経過時間を表示
    Console.WriteLine(endTime - startTime);
  }
}
実行結果例
00:00:03.0026460

大雑把な経過時間が把握できればよい場合はこの方法で十分ですが、より高い精度で計測したい場合はStopwatchクラスを使います。 経過時間の計測についてはランタイム・システム・プラットフォームの情報 §.経過時間でも解説しています。

なお、DateTime・DateTimeOffset同士での減算を行う場合、結果は単純な数値型ではなくTimeSpan型となります。

§2.2 最小値・最大値・精度

DateTime・DateTimeOffsetでは、最小で "0001年1月1日 0時0分0秒"、最大で "9999年12月31日 23時59分59秒" までの範囲の日時を扱うことが出来ます。 この最小値・最大値はMinValueフィールドおよびMaxValueフィールドを参照することで取得できます。

using System;

class Sample {
  static void Main()
  {
    Console.WriteLine("[DateTime]");
    Console.WriteLine(DateTime.MinValue);
    Console.WriteLine(DateTime.MaxValue);
    Console.WriteLine();

    Console.WriteLine("[DateTimeOffset]");
    Console.WriteLine(DateTimeOffset.MinValue);
    Console.WriteLine(DateTimeOffset.MaxValue);
  }
}
実行結果例
[DateTime]
0001/01/01 0:00:00
9999/12/31 23:59:59

[DateTimeOffset]
0001/01/01 0:00:00 +00:00
9999/12/31 23:59:59 +00:00

なお、DateTimeOffsetのオフセット部分は、最小値が-14時間、最大値が+14時間となっています。

また、DateTime・DateTimeOffsetで扱える日時の精度は100ナノ秒となっています。 Ticksプロパティを参照すると、日時を100ナノ秒単位での値で取得することが出来ます。 コンストラクタでも100ナノ秒単位の値を指定する事ができ、最小値である0001年1月1日 0時0分0秒に指定した値を加えた値がインスタンスの表す日時となります。

using System;

class Sample {
  static void Main()
  {

    Console.WriteLine("[DateTime]");
    Console.WriteLine(DateTime.MinValue.Ticks);
    Console.WriteLine(DateTime.MaxValue.Ticks);

    // 30,000,000 × 100ナノ秒 = 3秒
    Console.WriteLine(new DateTime(30000000));
    Console.WriteLine();

    Console.WriteLine("[DateTimeOffset]");
    Console.WriteLine(DateTimeOffset.MinValue.Ticks);
    Console.WriteLine(DateTimeOffset.MaxValue.Ticks);

    // 30,000,000 × 100ナノ秒 = 3秒
    Console.WriteLine(new DateTimeOffset(30000000, TimeSpan.Zero));
  }
}
実行結果例
[DateTime]
0
3155378975999999999
0001/01/01 0:00:03

[DateTimeOffset]
0
3155378975999999999
0001/01/01 0:00:03 +00:00

§2.3 日時の要素の取得

DateTime・DateTimeOffsetでは日時を扱える以上、日時から時分秒や年月日・曜日などを個別に扱うことも出来るようになっています。

§2.3.1 時分秒の取得

DateTime・DateTimeOffsetが表す日時の時分秒を参照するには、HourMinuteSecondの各プロパティを参照します。 Millisecondプロパティで秒の端数(ミリ秒部分)も取得することが出来ます。

using System;

class Sample {
  static void Main()
  {
    DateTime dt = DateTime.Now; // 現在の日時を取得

    Console.WriteLine("{0}時", dt.Hour);             // 現在時刻の時部分を取得
    Console.WriteLine("{0}分", dt.Minute);           // 現在時刻の分部分を取得
    Console.WriteLine("{0}秒", dt.Second);           // 現在時刻の秒部分を取得
    Console.WriteLine("{0}ミリ秒", dt.Millisecond);  // 現在時刻のミリ秒部分を取得
    Console.WriteLine();

    DateTimeOffset dto = DateTimeOffset.Now; // 現在の日時を取得

    Console.WriteLine("{0}時", dto.Hour);            // 現在時刻の時部分を取得
    Console.WriteLine("{0}分", dto.Minute);          // 現在時刻の分部分を取得
    Console.WriteLine("{0}秒", dto.Second);          // 現在時刻の秒部分を取得
    Console.WriteLine("{0}ミリ秒", dto.Millisecond); // 現在時刻のミリ秒部分を取得
  }
}
実行結果例
15時
0分
30秒
123ミリ秒

15時
0分
30秒
123ミリ秒

TimeOfDayプロパティでは、DateTimeの表す日時のうち、時刻の部分(午前0時ちょうどからの経過時間)のみをTimeSpanで取得することが出来ます。

using System;

class Sample {
  static void Main()
  {
    DateTime dt = DateTime.Now; // 現在の日時を取得

    TimeSpan timeOfDt = dt.TimeOfDay; // 現在の時刻のみを取得

    Console.WriteLine(timeOfDt);

    DateTimeOffset dto = DateTimeOffset.Now; // 現在の日時を取得

    TimeSpan timeOfDto = dto.TimeOfDay; // 現在の時刻のみを取得

    Console.WriteLine(timeOfDto);
  }
}
実行結果例
15:00:30.1230000
15:00:30.1230000

Ticksプロパティでは、DateTimeの最小値である0001年1月1日 0時0分0秒からの経過時間を100ナノ秒単位で取得することが出来ます。 なお、DateTimeOffsetにはUtcTicksプロパティが用意されていて、UTCに変換した時刻での経過時間を取得できます。 単位はTicksと同じく100ナノ秒です。

using System;

class Sample {
  static void Main()
  {
    DateTime dt = DateTime.Now; // 現在の日時を取得

    Console.WriteLine(dt.Ticks);
    Console.WriteLine();

    DateTimeOffset dto = DateTimeOffset.Now; // 現在の日時を取得

    Console.WriteLine(dto.Ticks);
    Console.WriteLine(dto.UtcTicks);
  }
}
実行結果例
635004252301230000

635004252301230000
635003928301230000

§2.3.2 年月日・曜日の取得

DateTime・DateTimeOffsetが表す日時の年月日を参照するには、YearMonthDayの各プロパティを参照します。 DayOfWeekプロパティで日付の曜日を取得することが出来ます。

日付が1月の場合、Monthプロパティは 1 を返します(1月が0となるC言語のtm構造体とは異なります)。 また、DayOfWeekプロパティが返す曜日は数値ではなく、DayOfWeek列挙体の値となります。

using System;

class Sample {
  static void Main()
  {
    DateTime dt = DateTime.Now; // 現在の日時を取得

    Console.WriteLine("{0}年", dt.Year);         // 現在日付の年部分を取得
    Console.WriteLine("{0}月", dt.Month);        // 現在日付の月部分を取得
    Console.WriteLine("{0}日", dt.Day);          // 現在日付の日部分を取得
    Console.WriteLine("{0}曜日", dt.DayOfWeek);  // 現在日付の曜日部分を取得
    Console.WriteLine();

    DateTimeOffset dto = DateTimeOffset.Now; // 現在の日時を取得

    Console.WriteLine("{0}年", dto.Year);         // 現在日付の年部分を取得
    Console.WriteLine("{0}月", dto.Month);        // 現在日付の月部分を取得
    Console.WriteLine("{0}日", dto.Day);          // 現在日付の日部分を取得
    Console.WriteLine("{0}曜日", dto.DayOfWeek);  // 現在日付の曜日部分を取得
  }
}
実行結果例
2013年
4月
1日
Monday曜日

2013年
4月
1日
Monday曜日

DayOfWeek列挙体と曜日、割り当てられている数値の対応は次のとおりです。

DayOfWeek列挙体と曜日の対応
DayOfWeek列挙体のメンバー 曜日
DayOfWeek.Sunday 日曜日 0
DayOfWeek.Monday 月曜日 1
DayOfWeek.Tuesday 火曜日 2
DayOfWeek.Wednesday 水曜日 3
DayOfWeek.Thursday 木曜日 4
DayOfWeek.Friday 金曜日 5
DayOfWeek.Saturday 土曜日 6

Dateプロパティでは、DateTimeの表す日時のうち、日付の部分のみ(時刻を0時0分0秒にした値)をDateTime型で取得することが出来ます。

using System;

class Sample {
  static void Main()
  {
    DateTime dt = DateTime.Now; // 現在の日時を取得

    Console.WriteLine(dt);
    Console.WriteLine(dt.Date); // 日付部分のみを取得
    Console.WriteLine();

    DateTimeOffset dto = DateTimeOffset.Now; // 現在の日時を取得

    Console.WriteLine(dto);
    Console.WriteLine(dto.Date); // 日付部分のみを取得
  }
}
実行結果例
2013/04/01 15:00:30
2013/04/01 0:00:00

2013/04/01 15:00:30 +09:00
2013/04/01 0:00:00

§2.3.2.1 月名・曜日名・年号の表記

月名を英語や他の外国語表記にしたり、曜日名を日本語表記にしたり、和暦での年号を付記したりするには、書式を指定した文字列化や特定カルチャの指定を行うことで出来ます。

using System;
using System.Globalization;
using System.Threading;

class Sample {
  static void Main()
  {
    DateTime dt = DateTime.Now; // 現在の日時を取得

    // スレッドのカルチャをen-US(英語/アメリカ合衆国)に変更
    Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US");

    Console.WriteLine("{0:D} {0:T}", dt);  // 日付と時刻を長い形式で表す標準の書式指定子
    Console.WriteLine(dt.ToString("MMMM")); // 月の完全名を表すカスタム書式指定子
    Console.WriteLine(dt.ToString("MMM"));  // 月の省略名を表すカスタム書式指定子
    Console.WriteLine(dt.ToString("dddd")); // 曜日の完全名を表すカスタム書式指定子
    Console.WriteLine(dt.ToString("ddd"));  // 曜日の省略名を表すカスタム書式指定子
    Console.WriteLine();

    // スレッドのカルチャは変更せず、カルチャを指定して文字列化
    CultureInfo jajp = new CultureInfo("ja-JP");

    Console.WriteLine(string.Format(jajp, "{0:D} {0:T}", dt));  // 日付と時刻を長い形式で表す標準の書式指定子
    Console.WriteLine(dt.ToString("MMMM", jajp)); // 月の完全名を表すカスタム書式指定子
    Console.WriteLine(dt.ToString("MMM", jajp));  // 月の省略名を表すカスタム書式指定子
    Console.WriteLine(dt.ToString("dddd", jajp)); // 曜日の完全名を表すカスタム書式指定子
    Console.WriteLine(dt.ToString("ddd", jajp));  // 曜日の省略名を表すカスタム書式指定子
    Console.WriteLine();

    Console.WriteLine(dt.ToString("dddd", new CultureInfo("de-DE")));
    Console.WriteLine(dt.ToString("dddd", new CultureInfo("fr-FR")));
    Console.WriteLine(dt.ToString("dddd", new CultureInfo("zh-TW")));
    Console.WriteLine();
  }
}
実行結果例
Monday, April 01, 2013 3:00:30 PM
April
Apr
Monday
Mon

2013年4月1日 15:00:30
4月
4
月曜日
月

Montag
lundi
星期一
using System;
using System.Globalization;

class Sample {
  static void Main()
  {
    DateTime dt = DateTime.Now; // 現在の日時を取得

    // ja-JPのCultureInfoを作成
    CultureInfo jajp = new CultureInfo("ja-JP");

    // カルチャを指定して文字列化
    Console.WriteLine(dt.ToString("gg yyyy", jajp)); // 年号+年4桁

    // JapaneseCalendarで定義される日付と時刻の書式を使用するように変更
    jajp.DateTimeFormat.Calendar = new JapaneseCalendar();

    // カルチャを指定して文字列化
    Console.WriteLine(dt.ToString("gg yyyy", jajp)); // 年号+年4桁
  }
}
実行結果例
西暦 2013
平成 0025

§2.4 うるう年・通算日数・夏時間

DateTimeにはうるう年・夏時間かどうかの判定を行うメソッドや、通算日数を求めるプロパティなど、日付を処理する上で便利なメンバーが用意されています。 一方、いくつかのメソッド・プロパティはDateTimeOffsetには用意されていないため、必要に応じてDateTimeに変換してからこれらのメンバーを参照します。

§2.4.1 うるう年

ある年がうるう年かどうかを判別するには、DateTimeの静的メソッドであるIsLeapYearメソッドを使うことが出来ます。 このメソッドは静的メソッドで、引数には年を表す数値を指定します。 次の例では、2010年〜2020年の各年について、その年がうるう年かどうかを調べて表示しています。

using System;

class Sample {
  static void Main()
  {
    for (int year = 2010; year <= 2020; year++) {
      // 2010年〜2020年の各年がうるう年かどうかを求める
      Console.WriteLine("IsLeapYear({0}) : {1}", year, DateTime.IsLeapYear(year));
    }
  }
}
実行結果
IsLeapYear(2010) : False
IsLeapYear(2011) : False
IsLeapYear(2012) : True
IsLeapYear(2013) : False
IsLeapYear(2014) : False
IsLeapYear(2015) : False
IsLeapYear(2016) : True
IsLeapYear(2017) : False
IsLeapYear(2018) : False
IsLeapYear(2019) : False
IsLeapYear(2020) : True

DateTimeではグレゴリオ暦が使用されるため、IsLeapYearメソッドもグレゴリオ暦の暦法に従ってうるう年かどうかの判定が行われます。 グレゴリオ暦以外でのうるう年の判定を行う必要がある場合は、Calendarクラスを使います。

§2.4.2 通算日数

DayOfYearプロパティを参照することで、DateTime・DateTimeOffsetの表す日時がその年の通算何日目かを取得することが出来ます。

using System;

class Sample {
  static void Main()
  {
    DateTime dt = DateTime.Now; // 現在の日時を取得

    Console.WriteLine(dt);
    Console.WriteLine(dt.DayOfYear); // 今年の何日目か取得する
    Console.WriteLine();

    DateTimeOffset dto = DateTimeOffset.Now; // 現在の日時を取得

    Console.WriteLine(dto);
    Console.WriteLine(dto.DayOfYear); // 今年の何日目か取得する
  }
}
実行結果例
2013/04/01 15:00:30
91

2013/04/01 15:00:30 +09:00
91

DayOfYearプロパティは、うるう年の場合は追加されたうるう日の分も積算します。 次の例では2011年〜2013年の各年の4月1日を表すDateTimeに対して、その年がうるう年かどうかと、その日までの積算日数を表示しています。

using System;

class Sample {
  static void Main()
  {
    // 2011年〜2013年の各年の4月1日
    DateTime dt20110401 = new DateTime(2011, 4, 1);
    DateTime dt20120401 = new DateTime(2012, 4, 1);
    DateTime dt20130401 = new DateTime(2013, 4, 1);

    Console.WriteLine("{0} ({1}) : {2}", dt20110401, DateTime.IsLeapYear(dt20110401.Year), dt20110401.DayOfYear);
    Console.WriteLine("{0} ({1}) : {2}", dt20120401, DateTime.IsLeapYear(dt20120401.Year), dt20120401.DayOfYear);
    Console.WriteLine("{0} ({1}) : {2}", dt20130401, DateTime.IsLeapYear(dt20130401.Year), dt20130401.DayOfYear);
  }
}
実行結果
2011/04/01 0:00:00 (False) : 91
2012/04/01 0:00:00 (True) : 92
2013/04/01 0:00:00 (False) : 91

§2.4.3 月ごとの日数

DaysInMonthメソッドを使うとある年ある月の日数を求めることが出来ます。 このメソッドでは、指定された年がうるう年の場合は追加されたうるう日の分も含めた日数を返します。 次の例では2012年の各月ごとの日数を表示しています。

using System;

class Sample {
  static void Main()
  {
    for (int month = 1; month <= 12; month++) {
      // 2012年1月〜12月の日数を求める
      Console.WriteLine("2012-{0} : {1} days", month, DateTime.DaysInMonth(2012, month));
    }
  }
}
実行結果
2012-1 : 31 days
2012-2 : 29 days
2012-3 : 31 days
2012-4 : 30 days
2012-5 : 31 days
2012-6 : 30 days
2012-7 : 31 days
2012-8 : 31 days
2012-9 : 30 days
2012-10 : 31 days
2012-11 : 30 days
2012-12 : 31 days

このメソッドを用いることで、月末の日付を求めることも出来ます。 実装例は月末の日付を求めるで紹介しています。

§2.4.4 夏時間

IsDaylightSavingTimeメソッドを使うと、DateTimeの表す日時が夏時間の期間内かどうかを調べることができます。 このメソッドでは、実行環境に設定されているタイムゾーンに夏時間が導入されていなければ、当然どのような日付に対してもfalseを返します。 なお、実行環境のタイムゾーンに関する情報は、TimeZoneクラスおよびTimeZoneInfoクラスで参照することが出来ます。

using System;

class Sample {
  static void Main()
  {
    // 標準時間・夏時間のタイムゾーン名を表示
    Console.WriteLine("StandardName: {0}", TimeZone.CurrentTimeZone.StandardName);
    Console.WriteLine("DaylightName: {0}", TimeZone.CurrentTimeZone.DaylightName);

    // 冬期・夏期の日付に対してIsDaylightSavingTimeメソッドを呼び出して夏時間かどうか調べる
    DateTime winter = new DateTime(2013, 1, 1);
    DateTime summer = new DateTime(2013, 7, 1);

    Console.WriteLine("{0} {1}", winter, winter.IsDaylightSavingTime());
    Console.WriteLine("{0} {1}", summer, summer.IsDaylightSavingTime());
  }
}
.NET Framework・タイムゾーンが「大阪、札幌、東京」での実行結果例
StandardName: 東京 (標準時)
DaylightName: 東京 (夏時間)
2013/01/01 0:00:00 False
2013/07/01 0:00:00 False
Mono・環境変数TZ=Asia/Tokyoでの実行結果例
StandardName: JST
DaylightName: JST
2013/01/01 0:00:00 False
2013/07/01 0:00:00 False
Mono・環境変数TZ=America/Los_Angelesでの実行結果例
StandardName: PST
DaylightName: PDT
2013/01/01 0:00:00 False
2013/07/01 0:00:00 True

実行環境に設定されているものとは異なるタイムゾーンにおける日時が夏時間の期間内かどうかを調べるには、TimeZoneInfo.IsDaylightSavingTimeメソッドを使います。

§2.5 日時の値の変更・加減算

年月日や時分秒を表すHour・Day・Monthなどのプロパティは参照専用で、インスタンスを作成した後は一切変更することができません。 年月日・時分秒の一部分だけを変更したい場合は、変更したい値をコンストラクタに指定して新たにインスタンスを作成する必要があります。

using System;

class Sample {
  static void Main()
  {
    DateTime now = DateTime.Now; // 現在日時を取得

    // 明日同時刻のインスタンスを作成
    DateTime oneDayLater = new DateTime(now.Year, now.Month, now.Day + 1, now.Hour, now.Minute, now.Second);

    Console.WriteLine(now);
    Console.WriteLine(oneDayLater);
  }
}
実行結果例
2013/04/01 15:00:30
2013/04/02 15:00:30

上記の例では、明日同時刻のDateTimeを作成していますが、月末に実行すると例外ArgumentOutOfRangeExceptionがスローされます。 例えば4月30日では、単純に日にちに1足すと4月31日となり、コンストラクタに不正な日付を指定することになるためです。

このように、既存のDateTimeの値を足し引きしてコンストラクタに指定する場合は、月替わりや日付変更・正時を跨ぐような場合に値が範囲外とならないよう考慮する必要があります。 一方、DateTime.AddDays等のメソッドで日時の加減算を行えば、そういった日時の境界を跨ぐ加減算も容易に行えます。

§2.5.1 日時の加減算

コンストラクタで具体的な日時を指定する他にも、基準となる日時から加減算して目的の日時のインスタンスを作成することも出来ます。 例えば、AddDaysメソッドを使用するとインスタンスに指定した日数を足した日時を取得することが出来るため、このメソッドを使うと上記の例は次のように書き換えることが出来ます。

using System;

class Sample {
  static void Main()
  {
    DateTime now = DateTime.Now; // 現在日時を取得

    // 明日同時刻のインスタンスを作成
    DateTime oneDayLater = now.AddDays(1.0);

    Console.WriteLine(now);
    Console.WriteLine(oneDayLater);
  }
}
実行結果例
2013/04/01 15:00:30
2013/04/02 15:00:30

AddDaysメソッドでは、月替りを跨ぐような加算も正しく行われます。 例えば上記の例を4月30日に実行すると、4月31日という不正な日付ではなく、5月1日という正しい日付が得られます。

AddDaysメソッドだけでなく、DateTime・DateTimeOffsetには日時に対して加減算を行うためのメソッドがいくつか用意されています。 次の表はそのようなメソッドをまとめたものです。

日時に対する加減算を行うメソッド
メソッド 機能 引数の型
DateTime.AddYears
DateTimeOffset.AddYears
指定された年数を加算した日時を求める int/Integer
DateTime.AddMonths
DateTimeOffset.AddMonths
指定された月数を加算した日時を求める int/Integer
DateTime.AddDays
DateTimeOffset.AddDays
指定された日数を加算した日時を求める double/Double
DateTime.AddHours
DateTimeOffset.AddHours
指定された時間数を加算した日時を求める double/Double
DateTime.AddMinutes
DateTimeOffset.AddMinutes
指定された分数を加算した日時を求める double/Double
DateTime.AddSeconds
DateTimeOffset.AddSeconds
指定された秒数を加算した日時を求める double/Double
DateTime.AddMilliseconds
DateTimeOffset.AddMilliseconds
指定されたミリ秒数を加算した日時を求める double/Double
DateTime.AddTicks
DateTimeOffset.AddTicks
指定されたタイマ刻み数(100ナノ秒単位)を加算した日時を求める long/Long
DateTime.Add
DateTimeOffset.Add
指定された時間間隔を加算した日時を求める TimeSpan
DateTime.Subtract
DateTimeOffset.Subtract
指定された時間間隔を減算した日時を求める TimeSpan

これらのメソッドのうちいくつかは引数にdoubleを取るものが用意されているため、1.5日後や8.5時間後といった日時を求めることが出来ます。 正数だけでなく負数も指定することが出来るため、これらのメソッドを使ってある日時から3日前(= -3日後)、5年前(= -5年後)といった日時を求めることも出来ます。

またDateTime・DateTimeOffsetのコンストラクタでは月であれば1〜12、時間であれば0〜23の範囲内の値を指定しなければArgumentOutOfRangeExceptionがスローされますが、Add*メソッドではその範囲外の値も指定出来るため、45日後、26時間後、といった日時を求めることも出来ます。 Add*メソッドではうるう年での日数の考慮も行われます。

using System;

class Sample {
  static void Main()
  {
    DateTime now = DateTime.Now; // 現在日時を取得

    Console.WriteLine("now: {0}", now);
    Console.WriteLine("3 days before: {0}", now.AddDays(-3.0).Date); // 3日前の日付を求める
    Console.WriteLine();

    // 日時「2012年2月28日 32時の5分前」を正規化する
    DateTime dt = new DateTime(2012, 2, 28);

    dt = dt.AddHours(32.0); // 32時
    dt = dt.AddMinutes(-5.0); // 5分前

    Console.WriteLine(dt);
  }
}
実行結果例
now: 2013/04/01 15:00:30
3 days before: 2013/03/29 0:00:00

2012/02/29 7:55:00

当然ながらこれらのメソッドで日時の加減算を行なっても、その結果として得られるDateTimeのKindおよびDateTimeOffsetのOffsetは加減算を行う前のものと同じものとなります(日時の加減算はDateTime.KindおよびDateTimeOffset.Offsetには影響しません)。

Addメソッド・Subtractメソッドでは引数にTimeSpanを取るため、8時間30分5秒前や1日と8時間後といった日時を一度のメソッド呼び出しで求めることができます。 さらに、C#・VB.NETなどオーバーロードされた演算子を使用できる言語では、DateTime・DateTimeOffsetにTimeSpanを加減算するために加算演算子・減算演算子を使うこともできます。 当然、結果はAddメソッド・Subtractメソッドを使う場合と同じです。

using System;

class Sample {
  static void Main()
  {
    // +36時間を表すTimeSpan
    TimeSpan span = new TimeSpan(36, 0, 0);

    // DateTimeにTimeSpanを加算する
    DateTime dt = new DateTime(2012, 2, 28);

    Console.WriteLine(dt + span); // == dt.Add(span)

    // DateTimeOffsetにTimeSpanを加算する
    DateTimeOffset dto = new DateTimeOffset(2012, 2, 28, 0, 0, 0, TimeSpan.Zero);

    Console.WriteLine(dto + span); // == dto.Add(span)
  }
}
実行結果
2012/02/29 12:00:00
2012/02/29 12:00:00 +00:00

§2.5.1.1 AddYearsとうるう年の考慮

うるう年の2月29日を表すDateTime・DateTimeOffsetに対してAddYearsメソッドで年単位の加減算を行う場合は注意が必要です。 うるう年の2月29日から年単位の加減算をした結果、その年もうるう年となる場合は2月29日、そうでない場合は2月28日が返されます(3月1日にはなりません)。

AddYearsとn年前・n年後の2月29日
using System;

class Sample {
  static void Main()
  {
    var baseDate = new DateTime(2012, 2, 29);

    Console.WriteLine("{0} + 1 years = {1}", baseDate, baseDate.AddYears(+1));
    Console.WriteLine("{0} - 1 years = {1}", baseDate, baseDate.AddYears(-1));
    Console.WriteLine("{0} + 4 years = {1}", baseDate, baseDate.AddYears(+4));
    Console.WriteLine("{0} - 4 years = {1}", baseDate, baseDate.AddYears(-4));
    Console.WriteLine();

    baseDate = new DateTime(2016, 2, 29);

    Console.WriteLine("{0} + 1 years = {1}", baseDate, baseDate.AddYears(+1));
    Console.WriteLine("{0} - 1 years = {1}", baseDate, baseDate.AddYears(-1));
    Console.WriteLine("{0} + 4 years = {1}", baseDate, baseDate.AddYears(+4));
    Console.WriteLine("{0} - 4 years = {1}", baseDate, baseDate.AddYears(-4));
  }
}
実行結果
2012/02/29 0:00:00 + 1 years = 2013/02/28 0:00:00
2012/02/29 0:00:00 - 1 years = 2011/02/28 0:00:00
2012/02/29 0:00:00 + 4 years = 2016/02/29 0:00:00
2012/02/29 0:00:00 - 4 years = 2008/02/29 0:00:00

2016/02/29 0:00:00 + 1 years = 2017/02/28 0:00:00
2016/02/29 0:00:00 - 1 years = 2015/02/28 0:00:00
2016/02/29 0:00:00 + 4 years = 2020/02/29 0:00:00
2016/02/29 0:00:00 - 4 years = 2012/02/29 0:00:00

一方、2月28日または3月1日を表すDateTime・DateTimeOffsetに対してAddYearsメソッドを用いる場合は、その年がうるう年となるか否かに関わらず、結果は2月28日または3月1日となります。

AddYearsとn年前・n年後の2月28日・3月1日
using System;

class Sample {
  static void Main()
  {
    var baseDate = new DateTime(2012, 2, 28);

    Console.WriteLine("{0} + 1 years = {1}", baseDate, baseDate.AddYears(+1));
    Console.WriteLine("{0} - 1 years = {1}", baseDate, baseDate.AddYears(-1));
    Console.WriteLine("{0} + 4 years = {1}", baseDate, baseDate.AddYears(+4));
    Console.WriteLine("{0} - 4 years = {1}", baseDate, baseDate.AddYears(-4));
    Console.WriteLine();

    baseDate = new DateTime(2016, 3, 1);

    Console.WriteLine("{0} + 1 years = {1}", baseDate, baseDate.AddYears(+1));
    Console.WriteLine("{0} - 1 years = {1}", baseDate, baseDate.AddYears(-1));
    Console.WriteLine("{0} + 4 years = {1}", baseDate, baseDate.AddYears(+4));
    Console.WriteLine("{0} - 4 years = {1}", baseDate, baseDate.AddYears(-4));
  }
}
実行結果
2012/02/28 0:00:00 + 1 years = 2013/02/28 0:00:00
2012/02/28 0:00:00 - 1 years = 2011/02/28 0:00:00
2012/02/28 0:00:00 + 4 years = 2016/02/28 0:00:00
2012/02/28 0:00:00 - 4 years = 2008/02/28 0:00:00

2016/03/01 0:00:00 + 1 years = 2017/03/01 0:00:00
2016/03/01 0:00:00 - 1 years = 2015/03/01 0:00:00
2016/03/01 0:00:00 + 4 years = 2020/03/01 0:00:00
2016/03/01 0:00:00 - 4 years = 2012/03/01 0:00:00

うるう年かどうかの判別については§.うるう年を参照してください。

§2.5.1.2 AddMonthsと月ごとの日数の考慮

AddMonthsメソッドで月単位の加減算を行った結果その月の末日を超える場合は、日付の部分はその月の末日となります。 例えば、1月31日から3ヶ月加算した場合、4月31日は4月の末日を超えるため結果は4月30日となります。 同様に、1月31日から1ヶ月加算した場合、結果はうるう年であれば2月28日、そうでなければ2月29日となります。

AddMonthsと1月31日のnヶ月後
using System;

class Sample {
  static void Main()
  {
    var baseDate = new DateTime(2015, 1, 31);

    Console.WriteLine("{0} + 1 months = {1}", baseDate, baseDate.AddMonths(+1));
    Console.WriteLine("{0} + 2 months = {1}", baseDate, baseDate.AddMonths(+2));
    Console.WriteLine("{0} + 3 months = {1}", baseDate, baseDate.AddMonths(+3));
    Console.WriteLine();

    baseDate = new DateTime(2016, 1, 31);

    Console.WriteLine("{0} + 1 months = {1}", baseDate, baseDate.AddMonths(+1));
    Console.WriteLine("{0} + 2 months = {1}", baseDate, baseDate.AddMonths(+2));
    Console.WriteLine("{0} + 3 months = {1}", baseDate, baseDate.AddMonths(+3));
  }
}
実行結果
2015/01/31 0:00:00 + 1 months = 2015/02/28 0:00:00
2015/01/31 0:00:00 + 2 months = 2015/03/31 0:00:00
2015/01/31 0:00:00 + 3 months = 2015/04/30 0:00:00

2016/01/31 0:00:00 + 1 months = 2016/02/29 0:00:00
2016/01/31 0:00:00 + 2 months = 2016/03/31 0:00:00
2016/01/31 0:00:00 + 3 months = 2016/04/30 0:00:00

§2.5.2 日時同士の差

DateTime.SubtractおよびDateTimeOffset.Subtractメソッドは、日時に対する減算を行う目的のほかにも、二つの日時同士の減算を行いその時間差を求める目的でも使うことができます。 日時同士の差を求めるため、このメソッドの戻り値は時間間隔を表すTimeSpanとなります。

このメソッドでDateTime同士の差を求める場合、時刻の種類(Kindプロパティの値)は考慮されないため、常に同一タイムゾーンの時刻として差が求められる点に注意が必要です。 DateTimeに設定されている時刻の種類も考慮して時間差を求めるには、ToUniversalTime・ToLocalTimeメソッドで日時の種類をローカル時刻かUTCのどちらかに合わせた上でSubstractメソッドを呼び出す必要があります。

using System;

class Sample {
  static void Main()
  {
    // 日時aと日時bの時間差を求める
    DateTime a = new DateTime(2013, 4, 5, 15, 0, 0);
    DateTime b = new DateTime(2013, 4, 3, 8, 30, 0);

    TimeSpan diff = a.Subtract(b);

    Console.WriteLine(diff);

    // 一方はローカル時刻、もう一方はUTCの異なる時刻同士の差を求める
    DateTime c = new DateTime(2013, 4, 5, 15, 0, 0, DateTimeKind.Local); // ローカル時刻
    DateTime d = new DateTime(2013, 4, 5, 15, 0, 0, DateTimeKind.Utc); // UTC

    diff = c.Subtract(d);

    Console.WriteLine(diff);

    // 両者をUTCに統一して時刻同士の差を求める
    DateTime uc = c.ToUniversalTime();
    DateTime ud = d.ToUniversalTime();

    diff = uc.Subtract(ud);

    Console.WriteLine(diff);
  }
}
実行結果
2.06:30:00
00:00:00
-09:00:00

一方DateTimeOffset同士の場合は、Substractメソッドでの減算時にオフセット値も考慮されるため、事前にローカル時刻またはUTCに変換する必要はありません。 Substractメソッドでは内部的に一旦双方の値をUTCに変換した上でその差が求められます。

using System;

class Sample {
  static void Main()
  {
    // 日時aと日時bの時間差を求める
    DateTimeOffset a = new DateTimeOffset(2013, 4, 5, 15, 0, 0, TimeSpan.Zero);
    DateTimeOffset b = new DateTimeOffset(2013, 4, 3, 8, 30, 0, TimeSpan.Zero);

    TimeSpan diff = a.Subtract(b);

    Console.WriteLine(diff);

    // 一方はUTC+9、もう一方はUTC+0の異なる時刻同士の差を求める
    DateTimeOffset c = new DateTimeOffset(2013, 4, 5, 15, 0, 0, new TimeSpan(9, 0, 0));
    DateTimeOffset d = new DateTimeOffset(2013, 4, 5, 15, 0, 0, TimeSpan.Zero);

    diff = c.Subtract(d);

    Console.WriteLine(diff);
  }
}
実行結果
2.06:30:00
-09:00:00

なお、C#・VB.NETなどオーバーロードされた演算子を使用できる言語では、Substractメソッドを呼び出す代わりに減算演算子を使うことも出来ます。 結果はSubstractメソッドを使う場合と同じです。

using System;

class Sample {
  static void Main()
  {
    // DateTime同士の減算
    DateTime a = new DateTime(2013, 4, 5, 15, 0, 0);
    DateTime b = new DateTime(2013, 4, 3, 8, 30, 0);

    Console.WriteLine(a - b); // == a.Subtract(b)

    // DateTimeOffset同士の減算
    DateTimeOffset c = new DateTimeOffset(2013, 4, 5, 15, 0, 0, TimeSpan.Zero);
    DateTimeOffset d = new DateTimeOffset(2013, 4, 3, 8, 30, 0, TimeSpan.Zero);

    Console.WriteLine(c - d); // == c.Subtract(d)
  }
}
実行結果
2.06:30:00
2.06:30:00

§2.6 日時同士の比較

§2.6.1 等価性の比較

二つのDateTime・DateTimeOffsetが等しいかどうか(同一の日時を表すかどうか)を調べるにはEqualsメソッドを使うことが出来ます。 このメソッドは、インスタンスメソッド・静的メソッドの二種類が用意されています。 C#・VB.NETなどオーバーロードされた演算子を使用できる言語では、Equalsメソッドの代わりに等価演算子(==)・不等価演算子(!=)を使って等価性の比較を行うことも出来ます。

Equalsメソッド(および等価・不等価演算子)でDateTime同士の比較を行う場合、DateTime同士の減算の場合と同様に時刻の種類(Kindプロパティの値)は考慮されないため、常に同一タイムゾーンの時刻として比較される点に注意が必要です。 二つのDateTime同士の比較においては、両者のTicksプロパティの値が同じであればDateTimeは等しいものとして扱われます。 DateTimeに設定されている時刻の種類も考慮して比較するには、ToUniversalTime・ToLocalTimeメソッドで日時の種類をローカル時刻かUTCのどちらかに合わせた上で比較を行う必要があります。

using System;

class Sample {
  static void Main()
  {
    // 二つのDateTimeを比較する
    DateTime a = new DateTime(2013, 4, 5, 15, 0, 0);
    DateTime b = new DateTime(2013, 4, 3, 8, 30, 0);

    Console.WriteLine("a = {0}", a);
    Console.WriteLine("b = {0}", b);
    Console.WriteLine("a == b : {0}", a == b);
    Console.WriteLine("a != b : {0}", a != b);
    Console.WriteLine("a.Equals(b) : {0}", a.Equals(b));
    Console.WriteLine("DateTime.Equals(a, b) : {0}", DateTime.Equals(a, b));
    Console.WriteLine();

    // 一方はローカル時刻、もう一方はUTCの異なる時刻同士を比較する
    DateTime c = new DateTime(2013, 4, 5, 15, 0, 0, DateTimeKind.Local); // ローカル時刻
    DateTime d = new DateTime(2013, 4, 5, 15, 0, 0, DateTimeKind.Utc); // UTC

    Console.WriteLine("c = {0}", c);
    Console.WriteLine("d = {0}", d);
    Console.WriteLine("c == d : {0}", c == d);
    Console.WriteLine();

    // 両者をUTCに統一して時刻同士を比較する
    DateTime uc = c.ToUniversalTime();
    DateTime ud = d.ToUniversalTime();

    Console.WriteLine("uc = {0}", uc);
    Console.WriteLine("ud = {0}", ud);
    Console.WriteLine("uc == ud : {0}", uc == ud);
    Console.WriteLine();
  }
}
実行結果
a = 2013/04/05 15:00:00
b = 2013/04/03 8:30:00
a == b : False
a != b : True
a.Equals(b) : False
DateTime.Equals(a, b) : False

c = 2013/04/05 15:00:00
d = 2013/04/05 15:00:00
c == d : True

uc = 2013/04/05 6:00:00
ud = 2013/04/05 15:00:00
uc == ud : False

一方DateTimeOffset同士の場合は、Equalsメソッドでの比較の際にオフセット値も考慮されるため、事前にローカル時刻またはUTCに変換する必要はありません。 Equalsメソッドでは内部的に一旦双方の値をUTCに変換した上で比較が行われます。 オフセット値も含めて完全に同一の日時かどうかを比較したい場合には、DateTimeOffset.EqualsExactメソッドを使うことが出来ます。

using System;

class Sample {
  static void Main()
  {
    // 二つのDateTimeOffsetを比較する
    DateTimeOffset a = new DateTimeOffset(2013, 4, 5, 15, 0, 0, TimeSpan.Zero);
    DateTimeOffset b = new DateTimeOffset(2013, 4, 3, 8, 30, 0, TimeSpan.Zero);

    Console.WriteLine("a = {0}", a);
    Console.WriteLine("b = {0}", b);
    Console.WriteLine("a == b : {0}", a == b);
    Console.WriteLine("a != b : {0}", a != b);
    Console.WriteLine("a.Equals(b) : {0}", a.Equals(b));
    Console.WriteLine("DateTimeOffset.Equals(a, b) : {0}", DateTimeOffset.Equals(a, b));
    Console.WriteLine();

    // 一方はUTC+9、もう一方はUTC+0の異なる時刻同士を比較する
    DateTimeOffset c = new DateTimeOffset(2013, 4, 5, 15, 0, 0, new TimeSpan(9, 0, 0));
    DateTimeOffset d = new DateTimeOffset(2013, 4, 5, 15, 0, 0, TimeSpan.Zero);

    Console.WriteLine("c = {0}", c);
    Console.WriteLine("d = {0}", d);
    Console.WriteLine("c == d : {0}", c == d);
    Console.WriteLine();

    // cと同一時刻でタイムゾーンが異なる時刻を比較する
    DateTimeOffset e = new DateTimeOffset(2013, 4, 5, 1, 0, 0, new TimeSpan(-5, 0, 0));

    Console.WriteLine("c = {0}", c);
    Console.WriteLine("e = {0}", e);
    Console.WriteLine("c.Equals(e)      : {0}", c.Equals(e));
    Console.WriteLine("c.EqualsExact(e) : {0}", c.EqualsExact(e));
  }
}
実行結果
a = 2013/04/05 15:00:00 +00:00
b = 2013/04/03 8:30:00 +00:00
a == b : False
a != b : True
a.Equals(b) : False
DateTimeOffset.Equals(a, b) : False

c = 2013/04/05 15:00:00 +09:00
d = 2013/04/05 15:00:00 +00:00
c == d : False

c = 2013/04/05 15:00:00 +09:00
e = 2013/04/05 1:00:00 -05:00
c.Equals(e)      : True
c.EqualsExact(e) : False

DateTime・DateTimeOffsetはIEquatableインターフェイスを実装しています。 IEquatableインターフェイスとより高度な等価性の比較処理については等価性の定義と比較も合わせてご覧ください。

§2.6.2 大小関係の比較

二つのDateTime・DateTimeOffsetの大小関係(日時の前後関係)を調べるには静的メソッドのCompareメソッド、インスタンスメソッドのCompareToメソッドを使うことが出来ます。 また、C#・VB.NETなどオーバーロードされた演算子を使用できる言語では、等価性の比較と同様に比較演算子も使うことが出来ます。 次の表は、DateTime・DateTimeOffsetの大小関係を求めるためのメソッド・演算子の一覧です。

DateTime・DateTimeOffsetの大小関係を求めるメソッド・演算子
メソッド・演算子 動作
x > y xy よりも後の日時の場合に真(true)となる
x >= y xy よりも、もしくは等しい日時の場合に真(true)となる
x < y xy よりも前の日時の場合に真(true)となる
x <= y xy よりも、もしくは等しい日時の場合に真(true)となる
DateTime.Compare(x, y)
DateTimeOffset.Compare(x, y)
x.Compare(y)
x.Compare(y)
xy よりも後の日時(x > y)の場合、正の値を返す
xy同じ日時(x = y)の場合、0を返す
xy よりも前の日時(x < y)の場合、負の値を返す

Compare・CompareToメソッド(および比較演算子)でDateTime同士の比較を行う場合、DateTime同士の減算の場合と同様に時刻の種類(Kindプロパティの値)は考慮されないため、常に同一タイムゾーンの時刻として比較される点に注意が必要です。 二つのDateTime同士の比較結果は、両者のTicksプロパティの値の大小を比較したものと同じとなります。 DateTimeに設定されている時刻の種類も考慮して比較するには、ToUniversalTime・ToLocalTimeメソッドで日時の種類をローカル時刻かUTCのどちらかに合わせた上で比較を行う必要があります。

using System;

class Sample {
  static void Main()
  {
    // 二つのDateTimeを比較する
    DateTime a = new DateTime(2013, 4, 5, 15, 0, 0);
    DateTime b = new DateTime(2013, 4, 3, 8, 30, 0);

    Console.WriteLine("a = {0}", a);
    Console.WriteLine("b = {0}", b);
    Console.WriteLine("a > b : {0}", a > b);
    Console.WriteLine("a <= b : {0}", a <= b);
    Console.WriteLine("a.CompareTo(b) : {0}", a.CompareTo(b));
    Console.WriteLine("DateTime.Compare(b) : {0}", DateTime.Compare(a, b));
    Console.WriteLine();

    // 一方はローカル時刻、もう一方はUTCの異なる時刻同士を比較する
    DateTime c = new DateTime(2013, 4, 5, 15, 0, 0, DateTimeKind.Local); // ローカル時刻
    DateTime d = new DateTime(2013, 4, 5, 15, 0, 0, DateTimeKind.Utc); // UTC

    Console.WriteLine("c = {0}", c);
    Console.WriteLine("d = {0}", d);
    Console.WriteLine("c < d : {0}", c < d);
    Console.WriteLine("c > d : {0}", c > d);
    Console.WriteLine("DateTime.Compare(c, d) : {0}", DateTime.Compare(c, d));
    Console.WriteLine();

    // 両者をUTCに統一して時刻同士を比較する
    DateTime uc = c.ToUniversalTime();
    DateTime ud = d.ToUniversalTime();

    Console.WriteLine("uc = {0}", uc);
    Console.WriteLine("ud = {0}", ud);
    Console.WriteLine("uc < ud : {0}", uc < ud);
    Console.WriteLine("uc > ud : {0}", uc > ud);
    Console.WriteLine("DateTime.Compare(uc, ud) : {0}", DateTime.Compare(uc, ud));
    Console.WriteLine();
  }
}
実行結果
a = 2013/04/05 15:00:00
b = 2013/04/03 8:30:00
a > b : True
a <= b : False
a.CompareTo(b) : 1
DateTime.Compare(b) : 1

c = 2013/04/05 15:00:00
d = 2013/04/05 15:00:00
c < d : False
c > d : False
DateTime.Compare(c, d) : 0

uc = 2013/04/05 6:00:00
ud = 2013/04/05 15:00:00
uc < ud : True
uc > ud : False
DateTime.Compare(uc, ud) : -1

一方DateTimeOffset同士の場合は、Compare・CompareToメソッドでの比較の際にオフセット値も考慮されるため、事前にローカル時刻またはUTCに変換する必要はありません。 Compare・CompareToメソッドでは内部的に一旦双方の値をUTCに変換した上で比較が行われます。

using System;

class Sample {
  static void Main()
  {
    // 二つのDateTimeOffsetを比較する
    DateTimeOffset a = new DateTimeOffset(2013, 4, 5, 15, 0, 0, TimeSpan.Zero);
    DateTimeOffset b = new DateTimeOffset(2013, 4, 3, 8, 30, 0, TimeSpan.Zero);

    Console.WriteLine("a = {0}", a);
    Console.WriteLine("b = {0}", b);
    Console.WriteLine("a > b : {0}", a > b);
    Console.WriteLine("a < b : {0}", a < b);
    Console.WriteLine("a.CompareTo(b) : {0}", a.CompareTo(b));
    Console.WriteLine("DateTimeOffset.Compare(a, b) : {0}", DateTimeOffset.Compare(a, b));
    Console.WriteLine();

    // 一方はUTC+9、もう一方はUTC+0の異なる時刻同士を比較する
    DateTimeOffset c = new DateTimeOffset(2013, 4, 5, 15, 0, 0, new TimeSpan(9, 0, 0));
    DateTimeOffset d = new DateTimeOffset(2013, 4, 5, 15, 0, 0, TimeSpan.Zero);

    Console.WriteLine("c = {0}", c);
    Console.WriteLine("d = {0}", d);
    Console.WriteLine("c < d : {0}", c > d);
    Console.WriteLine("c > d : {0}", c < d);
    Console.WriteLine("DateTimeOffset.Compare(c, d) : {0}", DateTimeOffset.Compare(c, d));
    Console.WriteLine();

    // cと同一時刻でタイムゾーンが異なる時刻を比較する
    DateTimeOffset e = new DateTimeOffset(2013, 4, 5, 1, 0, 0, new TimeSpan(-5, 0, 0));

    Console.WriteLine("c = {0}", c);
    Console.WriteLine("e = {0}", e);
    Console.WriteLine("c < e : {0}", c > e);
    Console.WriteLine("c > e : {0}", c < e);
    Console.WriteLine("DateTimeOffset.Compare(c, e) : {0}", DateTimeOffset.Compare(c, e));
  }
}
実行結果
a = 2013/04/05 15:00:00 +00:00
b = 2013/04/03 8:30:00 +00:00
a > b : True
a < b : False
a.CompareTo(b) : 1
DateTimeOffset.Compare(a, b) : 1

c = 2013/04/05 15:00:00 +09:00
d = 2013/04/05 15:00:00 +00:00
c < d : False
c > d : True
DateTimeOffset.Compare(c, d) : -1

c = 2013/04/05 15:00:00 +09:00
e = 2013/04/05 1:00:00 -05:00
c < e : False
c > e : False
DateTimeOffset.Compare(c, e) : 0

DateTime・DateTimeOffsetはIComparableインターフェイスを実装しています。 IComparableインターフェイスとより高度な大小関係の比較処理については大小関係の定義と比較も合わせてご覧ください。

また、DateTime・DateTimeOffsetとソートについては基本型のソートと昇順・降順でのソートおよび基本型とデフォルトのソート順 §.日付型をご覧ください。

§2.6.3 日付のみ・時刻のみの比較

DateTime・DateTimeOffsetの日付のみ・時刻のみを比較するメソッドは用意されていませんが、Dateプロパティで日付のみ、TimeOfDayプロパティで時刻のみを取得できるため、これを使って比較することが出来ます。

なお、DateTimeの時刻の種類(Kindプロパティの値)はDateTime.Dateプロパティが返す値には影響しませんが、DateTimeOffset.DateプロパティはDateTimeOffsetに設定されているオフセット値を加算した上での日付を返す点に注意が必要です。

using System;

class Sample {
  static void Main()
  {
    // 各DateTimeの日付のみを比較する
    DateTime a = new DateTime(2013, 4, 5, 15, 0, 0);
    DateTime b = new DateTime(2013, 4, 3, 0, 0, 0, DateTimeKind.Local);
    DateTime c = new DateTime(2013, 4, 3, 8, 30, 0, DateTimeKind.Utc);

    Console.WriteLine("a      = {0}", a);
    Console.WriteLine("b      = {0}", b);
    Console.WriteLine("c      = {0}", c);
    Console.WriteLine("a.Date = {0}", a.Date);
    Console.WriteLine("b.Date = {0}", b.Date);
    Console.WriteLine("c.Date = {0}", c.Date);

    Console.WriteLine("DateTime.Compare(a, b)      : {0}", DateTime.Compare(a, b));
    Console.WriteLine("DateTime.Compare(b, c)      : {0}", DateTime.Compare(b, c));
    Console.WriteLine("DateTime.Compare(c, a)      : {0}", DateTime.Compare(c, a));
    Console.WriteLine("DateTime.Compare(a.Date, b.Date) : {0}", DateTime.Compare(a.Date, b.Date));
    Console.WriteLine("DateTime.Compare(b.Date, c.Date) : {0}", DateTime.Compare(b.Date, c.Date));
    Console.WriteLine("DateTime.Compare(c.Date, a.Date) : {0}", DateTime.Compare(c.Date, a.Date));
    Console.WriteLine();
  }
}
実行結果
a      = 2013/04/05 15:00:00
b      = 2013/04/03 0:00:00
c      = 2013/04/03 8:30:00
a.Date = 2013/04/05 0:00:00
b.Date = 2013/04/03 0:00:00
c.Date = 2013/04/03 0:00:00
DateTime.Compare(a, b) : 1
DateTime.Compare(b, c) : -1
DateTime.Compare(c, a) : -1
DateTime.Compare(a.Date, b.Date) : 1
DateTime.Compare(b.Date, c.Date) : 0
DateTime.Compare(c.Date, a.Date) : -1
using System;

class Sample {
  static void Main()
  {
    // 各DateTimeOffsetの日付のみを比較する
    DateTimeOffset a = new DateTimeOffset(2013, 4, 5, 6, 0, 0, new TimeSpan(9, 0, 0));
    DateTimeOffset b = new DateTimeOffset(2013, 4, 4, 21, 0, 0, TimeSpan.Zero);
    DateTimeOffset c = new DateTimeOffset(2013, 4, 4, 16, 0, 0, new TimeSpan(-5, 0, 0));

    Console.WriteLine("a      = {0}", a);
    Console.WriteLine("b      = {0}", b);
    Console.WriteLine("c      = {0}", c);
    Console.WriteLine("a.Date = {0}", a.Date);
    Console.WriteLine("b.Date = {0}", b.Date);
    Console.WriteLine("c.Date = {0}", c.Date);

    Console.WriteLine("DateTimeOffset.Compare(a, b) : {0}", DateTimeOffset.Compare(a, b));
    Console.WriteLine("DateTimeOffset.Compare(b, c) : {0}", DateTimeOffset.Compare(b, c));
    Console.WriteLine("DateTimeOffset.Compare(c, a) : {0}", DateTimeOffset.Compare(c, a));
    Console.WriteLine("DateTime.Compare(a.Date, b.Date) : {0}", DateTime.Compare(a.Date, b.Date));
    Console.WriteLine("DateTime.Compare(b.Date, c.Date) : {0}", DateTime.Compare(b.Date, c.Date));
    Console.WriteLine("DateTime.Compare(c.Date, a.Date) : {0}", DateTime.Compare(c.Date, a.Date));
  }
}
実行結果
a      = 2013/04/05 6:00:00 +09:00
b      = 2013/04/04 21:00:00 +00:00
c      = 2013/04/04 16:00:00 -05:00
a.Date = 2013/04/05 0:00:00
b.Date = 2013/04/04 0:00:00
c.Date = 2013/04/04 0:00:00
DateTimeOffset.Compare(a, b) : 0
DateTimeOffset.Compare(b, c) : 0
DateTimeOffset.Compare(c, a) : 0
DateTime.Compare(a.Date, b.Date) : 1
DateTime.Compare(b.Date, c.Date) : 0
DateTime.Compare(c.Date, a.Date) : -1

Dateプロパティとは異なり、DateTime.TimeOfDayプロパティおよびDateTimeOffset.TimeOfDayプロパティが返す値は、どちらも時刻の種類(Kindプロパティ)・オフセット値(Offsetプロパティ)の影響を受けず、時刻部分の値がそのままの値で返されます。 なお、TimeOfDayプロパティはTimeSpan型で時刻を返すため、メソッドを使って比較する場合はDateTime.CompareではなくTimeSpan.Compareを使います(TimeSpan.Compare以外にも比較演算子を使って比較することも出来ます)。

using System;

class Sample {
  static void Main()
  {
    // 各DateTimeの日付のみを比較する
    DateTime a = new DateTime(2013, 4, 5, 15, 0, 0);
    DateTime b = new DateTime(2013, 4, 3, 0, 0, 0, DateTimeKind.Local);
    DateTime c = new DateTime(2013, 4, 3, 8, 30, 0, DateTimeKind.Utc);

    Console.WriteLine("a = {0}", a);
    Console.WriteLine("b = {0}", b);
    Console.WriteLine("c = {0}", c);
    Console.WriteLine("a.TimeOfDay = {0}", a.TimeOfDay);
    Console.WriteLine("b.TimeOfDay = {0}", b.TimeOfDay);
    Console.WriteLine("c.TimeOfDay = {0}", c.TimeOfDay);

    Console.WriteLine("a.TimeOfDay < b.TimeOfDay : {0}", a.TimeOfDay < b.TimeOfDay);
    Console.WriteLine("b.TimeOfDay < c.TimeOfDay : {0}", b.TimeOfDay < c.TimeOfDay);
    Console.WriteLine("c.TimeOfDay < a.TimeOfDay : {0}", c.TimeOfDay < a.TimeOfDay);
    Console.WriteLine("TimeSpan.Compare(a.TimeOfDay, b.TimeOfDay) : {0}", TimeSpan.Compare(a.TimeOfDay, b.TimeOfDay));
    Console.WriteLine("TimeSpan.Compare(b.TimeOfDay, c.TimeOfDay) : {0}", TimeSpan.Compare(b.TimeOfDay, c.TimeOfDay));
    Console.WriteLine("TimeSpan.Compare(c.TimeOfDay, a.TimeOfDay) : {0}", TimeSpan.Compare(c.TimeOfDay, a.TimeOfDay));
  }
}
実行結果
a = 2013/04/05 15:00:00
b = 2013/04/03 0:00:00
c = 2013/04/03 8:30:00
a.TimeOfDay = 15:00:00
b.TimeOfDay = 00:00:00
c.TimeOfDay = 08:30:00
a.TimeOfDay < b.TimeOfDay : False
b.TimeOfDay < c.TimeOfDay : True
c.TimeOfDay < a.TimeOfDay : True
TimeSpan.Compare(a.TimeOfDay, b.TimeOfDay) : 1
TimeSpan.Compare(b.TimeOfDay, c.TimeOfDay) : -1
TimeSpan.Compare(c.TimeOfDay, a.TimeOfDay) : -1
using System;

class Sample {
  static void Main()
  {
    // 各DateTimeの日付のみを比較する
    DateTimeOffset a = new DateTimeOffset(2013, 4, 5, 6, 0, 0, new TimeSpan(9, 0, 0));
    DateTimeOffset b = new DateTimeOffset(2013, 4, 4, 21, 0, 0, TimeSpan.Zero);
    DateTimeOffset c = new DateTimeOffset(2013, 4, 4, 16, 0, 0, new TimeSpan(-5, 0, 0));

    Console.WriteLine("a = {0}", a);
    Console.WriteLine("b = {0}", b);
    Console.WriteLine("c = {0}", c);
    Console.WriteLine("a.TimeOfDay = {0}", a.TimeOfDay);
    Console.WriteLine("b.TimeOfDay = {0}", b.TimeOfDay);
    Console.WriteLine("c.TimeOfDay = {0}", c.TimeOfDay);

    Console.WriteLine("a.TimeOfDay < b.TimeOfDay : {0}", a.TimeOfDay < b.TimeOfDay);
    Console.WriteLine("b.TimeOfDay < c.TimeOfDay : {0}", b.TimeOfDay < c.TimeOfDay);
    Console.WriteLine("c.TimeOfDay < a.TimeOfDay : {0}", c.TimeOfDay < a.TimeOfDay);
    Console.WriteLine("TimeSpan.Compare(a.TimeOfDay, b.TimeOfDay) : {0}", TimeSpan.Compare(a.TimeOfDay, b.TimeOfDay));
    Console.WriteLine("TimeSpan.Compare(b.TimeOfDay, c.TimeOfDay) : {0}", TimeSpan.Compare(b.TimeOfDay, c.TimeOfDay));
    Console.WriteLine("TimeSpan.Compare(c.TimeOfDay, a.TimeOfDay) : {0}", TimeSpan.Compare(c.TimeOfDay, a.TimeOfDay));
  }
}
実行結果
a = 2013/04/05 6:00:00 +09:00
b = 2013/04/04 21:00:00 +00:00
c = 2013/04/04 16:00:00 -05:00
a.TimeOfDay = 06:00:00
b.TimeOfDay = 21:00:00
c.TimeOfDay = 16:00:00
a.TimeOfDay < b.TimeOfDay : True
b.TimeOfDay < c.TimeOfDay : False
c.TimeOfDay < a.TimeOfDay : False
TimeSpan.Compare(a.TimeOfDay, b.TimeOfDay) : -1
TimeSpan.Compare(b.TimeOfDay, c.TimeOfDay) : 1
TimeSpan.Compare(c.TimeOfDay, a.TimeOfDay) : 1

§2.6.3.1 時刻のみのソート

Array.SortメソッドなどでDateTime・DateTimeOffsetをソートする場合、デフォルトでは日時の小さい順にソートされます。 時刻のみの大小関係でソートしたい場合は、次のようにTimeOfDayプロパティの値を比較するメソッドを用意してそれをSortメソッドに渡すようにします。

using System;

class Sample {
  // DateTimeOffsetのTimeOfDayプロパティを比較するメソッド
  static int CompareTimeOfDay(DateTimeOffset x, DateTimeOffset y)
  {
    return TimeSpan.Compare(x.TimeOfDay, y.TimeOfDay);
  }

  static void Main()
  {
    // 各DateTimeOffsetの日付のみを比較する
    DateTimeOffset a = new DateTimeOffset(2013, 4, 5, 6, 0, 0, new TimeSpan(9, 0, 0));
    DateTimeOffset b = new DateTimeOffset(2013, 4, 4, 8, 0, 0, TimeSpan.Zero);
    DateTimeOffset c = new DateTimeOffset(2013, 4, 3, 11, 0, 0, new TimeSpan(-5, 0, 0));

    DateTimeOffset[] arr = new DateTimeOffset[] {a, b, c};

    // デフォルトの順序(日時の小さい順)でソート
    Array.Sort(arr);

    foreach (DateTimeOffset dto in arr) {
      Console.WriteLine("{0} (UTC: {1})", dto, dto.ToUniversalTime());
    }
    Console.WriteLine();

    // 時刻のみを比較してソート
    Array.Sort(arr, CompareTimeOfDay);

    foreach (DateTimeOffset dto in arr) {
      Console.WriteLine("{0} (TimeOfDay: {1})", dto, dto.TimeOfDay);
    }
    Console.WriteLine();
  }
}
実行結果
2013/04/03 11:00:00 -05:00 (UTC: 2013/04/03 16:00:00 +00:00)
2013/04/04 8:00:00 +00:00 (UTC: 2013/04/04 8:00:00 +00:00)
2013/04/05 6:00:00 +09:00 (UTC: 2013/04/04 21:00:00 +00:00)

2013/04/05 6:00:00 +09:00 (TimeOfDay: 06:00:00)
2013/04/04 8:00:00 +00:00 (TimeOfDay: 08:00:00)
2013/04/03 11:00:00 -05:00 (TimeOfDay: 11:00:00)

ソートについてより詳しくは、基本型のソートと昇順・降順でのソートおよび複合型のソート・複数キーでのソートなどをご覧ください。

§2.7 他のフォーマットとの相互変換

DateTime・DateTimeOffsetには日時を他の形式、例えば整数型や実数型の値などで表したものに変換するためのメソッドが用意されています。 以下のメソッドはそのようなメソッドの一覧です。

他のフォーマットとの相互変換を行うメソッド
変換を行うためのメソッド データ形式 解説
DateTime.ToFileTime
DateTime.FromFileTime
DateTimeOffset.ToFileTime
DateTimeOffset.FromFileTime
Windowsファイルタイムスタンプ形式
64ビットの整数値 (long型)
解説へ
DateTime.ToBinary
DateTime.FromBinary
DateTimeのバイナリ表現形式
64ビットのバイナリ値 (long型)
解説へ
DateTime.ToOADate
DateTime.FromOADate
OLEオートーメーションの日時形式
64ビットの浮動小数点値 (double型)
解説へ

これらのメソッドを使うことで、日時を他のフォーマットへ変換することが出来ます。 BinaryReaderクラス・BinaryWriterクラスで日時の読み書きを行う場合や、文字列以外の数値形式で日時を保存しておきたいといった目的でもこれらのメソッドを使うことが出来ます。

なお、文字列に変換するにはToStringメソッド、文字列からDateTime・DateTimeOffsetに変換するにはParseメソッドを使うことが出来ます。 文字列との相互変換については日時・文字列の変換と書式で詳しく解説します。

他にもConvertクラスを使った変換も行えますが、詳しくは基本型の型変換を参照してください。

DateTimeにはUNIX時間(UNIXタイムスタンプ)との相互変換を行うメソッドは用意されていません。 .NET Framework 4.6以降では、DateTimeOffsetにのみ用意されるToUnixTimeSecondsおよびFromUnixTimeSecondsメソッドを使うことでUNIX時間との相互変換を行うことができます。 このメソッドの詳細およびUNIX時間との相互変換を実装する方法についてはUNIX時間をDateTime型に変換するで解説しています。

§2.7.1 ToFileTime/FromFileTime

ToFileTime・FromFileTimeメソッドは、日時の値をWindowsのファイルタイムスタンプで使われるlong型(64ビット)の値に変換します。

タイムスタンプはUTCでの時刻とされているため、ToFileTimeメソッドではUTCに変換された値が返されます。 FromFileTimeメソッドでこの値を変換すると、UTCからローカル時刻に変換された(KindプロパティがDateTimeKind.Localの)DateTimeが返されます。 一方、FromFileTimeUtcメソッドで変換すると、UTC(KindプロパティがDateTimeKind.Utc)のDateTimeが返されます。

DateTimeOffset.FromFileTimeメソッドでは、返されるDateTimeOffsetのOffsetプロパティには常にローカル時刻のオフセットが設定されます。

using System;

class Sample {
  static void Main()
  {
    DateTime dt = DateTime.Now;
    long ts = dt.ToFileTime(); // DateTimeをファイルタイムスタンプの値に変換

    Console.WriteLine("{0:o} -> {1}", dt, ts);
    Console.WriteLine("{0} -> {1:o}", ts, DateTime.FromFileTime(ts));     // ファイルタイムスタンプの値をローカル時刻のDateTimeに変換
    Console.WriteLine("{0} -> {1:o}", ts, DateTime.FromFileTimeUtc(ts));  // ファイルタイムスタンプの値をUTCのDateTimeに変換
    Console.WriteLine();

    DateTimeOffset dto = DateTimeOffset.Now;
    ts = dto.ToFileTime(); // DateTimeOffsetをファイルタイムスタンプの値に変換

    Console.WriteLine("{0} -> {1}", dto, ts);
    Console.WriteLine("{0} -> {1}", ts, DateTimeOffset.FromFileTime(ts)); // ファイルタイムスタンプの値をDateTimeOffsetに変換
  }
}
実行結果例
2013-04-01T15:00:30.1230000+09:00 -> 130092696301230000
130092696301230000 -> 2013-04-01T15:00:30.1230000+09:00
130092696301230000 -> 2013-04-01T06:00:30.1230000Z

2013/04/01 15:00:30 +09:00 -> 130092696301230000
130092696301230000 -> 2013/04/01 15:00:30 +09:00

なお、File.SetLastWriteTimeなどのファイルのタイムスタンプを設定・取得するメソッドでは引数の型がDateTimeとなっています。 そのため、これらのメソッドを使ってタイムスタンプを変更する場合は、ToFileTime・FromFileTimeメソッドを使う必要はありません。

また、タイムスタンプで表現できる日時の範囲はDateTimeのものよりも狭いため、その範囲を越える値を変換しようとした場合はArgumentOutOfRangeExceptionがスローされます。

§2.7.2 ToBinary/FromBinary

ToFileTime・FromFileTimeメソッドは、日時の値をlong型(64ビット)の値に変換します。 このメソッドでは、DateTime.TicksプロパティおよびDateTime.Kindプロパティの値を64ビットのバイナリ値として格納したフォーマットが用いられます。 そのため、日時をバイナリデータとして保存・復元したい場合にはこのメソッドを使うことが出来ます。

using System;

class Sample {
  static void Main()
  {
    DateTime dt = DateTime.Now; // ローカル時刻
    long binary = dt.ToBinary();

    Console.WriteLine("{0:o} -> {1:x16}", dt, binary);
    Console.WriteLine("{0:x16} -> {1:o}", binary, DateTime.FromBinary(binary));
    Console.WriteLine();

    dt = DateTime.UtcNow; // UTC
    binary = dt.ToBinary();

    Console.WriteLine("{0:o} -> {1:x16}", dt, binary);
    Console.WriteLine("{0:x16} -> {1:o}", binary, DateTime.FromBinary(binary));
  }
}
実行結果例
2013-04-01T15:00:30.1230000+09:00 -> 88cffcb5595f57b0
88cffcb5595f57b0 -> 2013-04-01T15:00:30.1230000+09:00

2013-04-01T06:00:30.1230000Z -> 48cffcb5595f57b0
48cffcb5595f57b0 -> 2013-04-01T06:00:30.1230000Z

なお、ローカル時刻(KindプロパティがDateTimeKind.Local)のDateTimeをToBinaryメソッドで変換し、その値を変換時とは異なるタイムゾーンの環境で復元する場合、復元する環境のタイムゾーンでのローカル時刻として復元される点に注意が必要です。 これを避けるには、あらかじめ日時をUTCに変換しておく必要があります。

また、DateTimeOffsetにはToBinary・FromBinaryメソッドは用意されていません。 DateTimeOffsetのバイナリ表現が必要な場合は、DateTimeOffset.TicksプロパティおよびDateTimeOffset.Offsetプロパティの値を変換・復元します。

§2.7.3 ToOADate/FromOADate

ToFileTime・FromFileTimeメソッドは、日時の値をOLEオートメーション日時(double型・64ビット)の値に変換します。 このフォーマットでは時刻の種類は無視され、日時のみが変換されるという点に注意が必要です。 その他フォーマットの詳細などはToOADateメソッドのドキュメントを参照してください。

using System;

class Sample {
  static void Main()
  {
    DateTime dt = DateTime.Now;
    double oadate = dt.ToOADate();

    Console.WriteLine("{0:o} -> {1}", dt, oadate);
    Console.WriteLine("{0} -> {1:o}", oadate, DateTime.FromOADate(oadate));
  }
}
実行結果例
2013-04-01T15:00:30.1230000+09:00 -> 41365.6253486458
41365.6253486458 -> 2013-04-01T15:00:30.1230000

なお、OLEオートメーション形式で表現できる日時の範囲はDateTimeのものよりも狭いため、その範囲を越える値を変換しようとした場合はOverflowExceptionがスローされます。 (ArgumentOutOfRangeExceptionがスローされるToFileTimeメソッドとは異なる種類の例外がスローされる点に注意してください)

§2.7.4 アンマネージ呼び出し

DateTimeおよびDateTimeOffsetはアンマネージ呼び出しに使用することはできません。 Marshal.SizeOfメソッドによるサイズの取得や、Marshal.PtrToStructureメソッドによるポインタからの変換なども、例外エラーがスローされ、失敗します。

using System;
using System.Runtime.InteropServices;

class Sample {
  static void Main()
  {
    // DateTimeのサイズを取得しようとする
    Console.WriteLine(Marshal.SizeOf(typeof(DateTime)));
    // ハンドルされていない例外: System.ArgumentException: 型 'System.DateTime' はアンマネージ構造体としてマーシャリングできません。有効なサイズ、またはオフセットの計算ができません。
    //    場所 System.Runtime.InteropServices.Marshal.SizeOfHelper(Type t, Boolean throwIfNotMarshalable)
    //    場所 Sample.Main()

    // ポインタからDateTimeに変換しようとする
    IntPtr ptr = Marshal.AllocHGlobal(32); // 適当なサイズの領域を確保 (何らかのデータが格納されていると仮定)

    DateTime dt = (DateTime)Marshal.PtrToStructure(ptr, typeof(DateTime));
    // ハンドルされていない例外: System.ArgumentException: 指定された構造体は高速転送型か、またはレイアウト情報を含んでいなければなりません。
    // パラメーター名: structure
    //    場所 System.Runtime.InteropServices.Marshal.PtrToStructureHelper(IntPtr ptr, Object structure, Boolean allowValueClasses)
    //    場所 System.Runtime.InteropServices.Marshal.PtrToStructure(IntPtr ptr, Type structureType)
    //    場所 Sample.Main()
  }
}

アンマネージ呼び出しで日時の値を扱いたい場合は、DateTime・DateTimeOffsetをToFileTimeToBinaryなどのメソッドで変換したり、Ticksプロパティなどの値を用いたりするなど、値を別のフォーマットに変換する必要があります。

§3 TimeSpan

TimeSpan構造体は時間間隔、つまり時間の長さを扱うための型です。 ここでは、TimeSpanの機能と使い方について見ていきます。

§3.1 インスタンスの作成

TimeSpanのコンストラクタでは、時分秒に負の値を指定したり、36時間90分といった値を指定することができます。 負数や大きな値を指定した場合でも、TimeSpanの最大・最小値を越えない限り例外はスローされません。 時間に負の値・分に正の値といった指定もすることが出来ます。 時分秒単位以外にも、DateTime・DateTimeOffsetの精度と同じ単位であるタイマ刻み数(=100ナノ秒)単位の値を指定することも出来ます。

using System;

class Sample {
  static void Main()
  {
    // 3日と1時間30分
    TimeSpan a = new TimeSpan(3, 1, 30, 00);

    // -3時間+90分 (-1時間30分)
    TimeSpan b = new TimeSpan(-3, 90, 0);

    // 1500ミリ秒
    TimeSpan c = new TimeSpan(0, 0, 0, 0, 1500);

    // 30,000,000 × 100ナノ秒 = 3秒
    TimeSpan d = new TimeSpan(30000000);

    Console.WriteLine(a);
    Console.WriteLine(b);
    Console.WriteLine(c);
    Console.WriteLine(d);
  }
}
実行結果
3.01:30:00
-01:30:00
00:00:01.5000000
00:00:03

他にも、次のようなメソッドを使うことで時間間隔の単位を指定してインスタンスを生成することも出来ます。 FromTicks以外はdouble型の値を指定できるため、「1.5日」や「9.5時間」といった時間間隔の指定も可能です。

TimeSpanインスタンスを作成するためのメソッド
メソッド 機能
TimeSpan.FromDays 日数を指定してTimeSpanを作成する
TimeSpan.FromHours 時間数を指定してTimeSpanを作成する
TimeSpan.FromMinutes 分数を指定してTimeSpanを作成する
TimeSpan.FromSeconds 秒数を指定してTimeSpanを作成する
TimeSpan.FromMilliseconds ミリ秒数を指定してTimeSpanを作成する
TimeSpan.FromTicks タイマ刻み数(100ナノ秒単位)を指定してTimeSpanを作成する
using System;

class Sample {
  static void Main()
  {
    Console.WriteLine(TimeSpan.FromDays(1.5));    // 1.5日
    Console.WriteLine(TimeSpan.FromHours(9.5));   // 9.5時間
    Console.WriteLine(TimeSpan.FromMinutes(-80)); // -80分
  }
}
実行結果
1.12:00:00
09:30:00
-01:20:00

§3.2 合計時間・単位部分の取得

TimeSpanの表す時間間隔を日数・秒数などに換算した値が必要な場合は、Total*プロパティを参照します。 取得したい数値の単位に応じて、次のプロパティを参照することが出来ます。 これらのプロパティで得られる値はいずれもdoubleです。

合計時間を取得するプロパティ
プロパティ 取得できる値
TimeSpan.TotalDays TimeSpanの表す時間の長さを日数に換算した値を取得する
TimeSpan.TotalHours TimeSpanの表す時間の長さを時間数に換算した値を取得する
TimeSpan.TotalMinutes TimeSpanの表す時間の長さを分数に換算した値を取得する
TimeSpan.TotalSeconds TimeSpanの表す時間の長さを秒数に換算した値を取得する
TimeSpan.TotalMilliseconds TimeSpanの表す時間の長さをミリ秒数に換算した値を取得する
using System;

class Sample {
  static void Main()
  {
    TimeSpan ts = new TimeSpan(1, 2, 3, 4, 567);

    Console.WriteLine(ts);
    Console.WriteLine("= {0} 日", ts.TotalDays);
    Console.WriteLine("= {0} 時間", ts.TotalHours);
    Console.WriteLine("= {0} 分", ts.TotalMinutes);
    Console.WriteLine("= {0} 秒", ts.TotalSeconds);
    Console.WriteLine("= {0} ミリ秒", ts.TotalMilliseconds);
  }
}
実行結果
1.02:03:04.5670000
= 1.08546952546296 日
= 26.0512686111111 時間
= 1563.07611666667 分
= 93784.567 秒
= 93784567 ミリ秒

一方、TimeSpanの表す時間間隔の日部分・秒部分の値が必要な場合は、次のプロパティを参照します。 これらのプロパティで得られる値はいずれも端数なしの正規化された整数値で、例えばHoursなら常に0〜23の範囲、Minutes・Secondsなら0〜59の範囲で値が得られます。

各単位部分を取得するプロパティ
プロパティ 取得できる値
TimeSpan.Days TimeSpanの表す時間の長さの日部分を取得する
TimeSpan.Hours TimeSpanの表す時間の長さの時間部分を取得する
TimeSpan.Minutes TimeSpanの表す時間の長さの分部分を取得する
TimeSpan.Seconds TimeSpanの表す時間の長さの秒部分を取得する
TimeSpan.Milliseconds TimeSpanの表す時間の長さのミリ秒部分を取得する
using System;

class Sample {
  static void Main()
  {
    double seconds = 1234567.89;
    TimeSpan ts = TimeSpan.FromSeconds(seconds);

    Console.WriteLine("{0}秒 = {1} {2}時間 {3} {4}秒 .{5}",
                      seconds,
                      ts.Days, ts.Hours, ts.Minutes, ts.Seconds, ts.Milliseconds);
  }
}
実行結果
1234567.89秒 = 14日 6時間 56分 7秒 .890

§3.3 最小値・最大値・精度

TimeSpanでは、±Int64.MaxValue×100ナノ秒、おおよそ±10,675,199日の範囲の日時を扱うことが出来ます。 この最小値・最大値はMinValueフィールドおよびMaxValueフィールドを参照することで取得できます。 また、Zeroフィールドを参照することで長さ0の時間間隔を取得することも出来ます。

using System;

class Sample {
  static void Main()
  {
    Console.WriteLine(TimeSpan.MinValue);
    Console.WriteLine(TimeSpan.MaxValue);
    Console.WriteLine(TimeSpan.Zero);
  }
}
実行結果
-10675199.02:48:05.4775808
10675199.02:48:05.4775807
00:00:00

また、TimeSpanで扱える時間間隔の精度(タイマ刻み数)は、DateTime・DateTimeOffsetと同じく100ナノ秒となっています。 Ticksプロパティを参照すると、時間間隔を100ナノ秒単位での値で取得することが出来ます。

using System;

class Sample {
  static void Main()
  {
    TimeSpan ts = TimeSpan.FromSeconds(1);

    Console.WriteLine("{0} = {1} ticks", ts, ts.Ticks);

    Console.WriteLine("1 tick = {0}", TimeSpan.FromTicks(1));
  }
}
実行結果
00:00:01 = 10000000 ticks
1 tick = 00:00:00.0000001

なお、1時間あたりや1秒あたりのタイマ刻み数を取得する定数として、TicksPerHourフィールドTicksPerSecondフィールドなどが用意されています。

using System;

class Sample {
  static void Main()
  {
    Console.WriteLine("TicksPerMillisecond = {0}", TimeSpan.TicksPerMillisecond);
    Console.WriteLine("TicksPerSecond = {0}", TimeSpan.TicksPerSecond);
    Console.WriteLine("TicksPerMinute = {0}", TimeSpan.TicksPerMinute);
    Console.WriteLine("TicksPerHour = {0}", TimeSpan.TicksPerHour);
    Console.WriteLine("TicksPerDay = {0}", TimeSpan.TicksPerDay);
  }
}
実行結果
TicksPerMillisecond = 10000
TicksPerSecond = 10000000
TicksPerMinute = 600000000
TicksPerHour = 36000000000
TicksPerDay = 864000000000

§3.4 加減算

TimeSpanでは、TimeSpan同士の加減算を行うことが出来るようになっています。 Addメソッドで加算、Subtractメソッドで減算が行えます。 C#・VB.NETなどオーバーロードされた演算子を使用できる言語では、これらのメソッドを使う代わりに加算演算子・減算演算子を使うことも出来ます。

using System;

class Sample {
  static void Main()
  {
    TimeSpan a = TimeSpan.FromHours(24);
    TimeSpan b = TimeSpan.FromMinutes(90);

    Console.WriteLine("a = {0}", a);
    Console.WriteLine("b = {0}", b);
    Console.WriteLine("a.Add(b)      = {0}", a.Add(b));
    Console.WriteLine("a + b         = {0}", a + b);
    Console.WriteLine("a.Subtract(b) = {0}", a.Subtract(b));
    Console.WriteLine("a - b         = {0}", a - b);
  }
}
実行結果
a = 1.00:00:00
b = 01:30:00
a.Add(b)      = 1.01:30:00
a + b         = 1.01:30:00
a.Subtract(b) = 22:30:00
a - b         = 22:30:00

また、DateTime・DateTimeOffsetに対してTimeSpanを加減算することで、ある日時から一定時間経過した・遡った日時を求めることも出来ます。 (日時の加減算についてはDateTime.Addなどのメソッドを使うことも出来ます)

using System;

class Sample {
  static void Main()
  {
    // 現在日時
    DateTime dt = DateTime.Now;
    DateTimeOffset dto = DateTimeOffset.Now;

    TimeSpan ts = new TimeSpan(1, 8, 0, 0);

    // 現在から1日と8時間後の時刻を求める
    Console.WriteLine(dt + ts);
    Console.WriteLine(dto + ts);

    // 現在より1日と8時間前の時刻を求める
    Console.WriteLine(dt - ts);
    Console.WriteLine(dto - ts);
  }
}
実行結果例
2013/04/02 23:00:30
2013/04/02 23:00:30 +09:00
2013/03/31 7:00:30
2013/03/31 7:00:30 +09:00

この他、DurationメソッドでTimeSpanから符号を取り除いた絶対値、Negateメソッドで符号を反転した値を取得することが出来ます。

using System;

class Sample {
  static void Main()
  {
    TimeSpan ts = TimeSpan.FromHours(-27);

    Console.WriteLine(ts);
    Console.WriteLine(ts.Duration()); // tsの絶対値
    Console.WriteLine();

    ts = TimeSpan.FromMinutes(54);

    Console.WriteLine(ts);
    Console.WriteLine(ts.Negate()); // tsの符号を反転した値
  }
}
実行結果
-1.03:00:00
1.03:00:00

00:54:00
-00:54:00

§3.5 等価性・大小関係の比較

TimeSpanでは、二つのTimeSpanの値が同じかどうか、その等価性の比較を行うメソッドとしてEqualsメソッドが用意されています。 また、二つのTimeSpanのどちらが大きいか、その大小関係の比較を行うメソッドとしてCompareメソッドCompareToメソッドが用意されています。 これらのメソッドはIEquatableインターフェイス・IComparableインターフェイスのメソッドとして提供されます。 これらのメソッド・インターフェイスについての詳細は等価性の定義と比較および大小関係の定義と比較をご覧ください。

これらのメソッドを使う他にも、等価演算子(==)・不等価演算子(!=)・比較演算子(<, >)を使うことでTimeSpan同士の比較をすることも出来ます。

using System;

class Sample {
  static void Main()
  {
    TimeSpan a = new TimeSpan(3, 1, 30, 00);
    TimeSpan b = new TimeSpan(-3, 90, 0);

    Console.WriteLine("a = {0}", a);
    Console.WriteLine("b = {0}", b);
    Console.WriteLine("a == b : {0}", a == b);
    Console.WriteLine("a != b : {0}", a != b);
    Console.WriteLine("a < b : {0}", a < b);
    Console.WriteLine("a > b : {0}", a > b);
  }
}
実行結果
a = 3.01:30:00
b = -01:30:00
a == b : False
a != b : True
a < b : False
a > b : True

複数のTimeSpanをソートすることも出来ます。 TimeSpanでは、負から正の方向にTimeSpanの表す時間間隔の短い順(Ticksプロパティの値の小さい順)でソートされます。

using System;

class Sample {
  static void Main()
  {
    TimeSpan[] arr = new TimeSpan[] {
      new TimeSpan(3, 1, 30, 00),
      new TimeSpan(-3, 90, 0),
      new TimeSpan(0, 0, 0, 0, 1500),
      new TimeSpan(30000000),
    };

    foreach (TimeSpan ts in arr) {
      Console.WriteLine(ts);
    }
    Console.WriteLine();

    // Array.Sortを使ってTimeSpanをソート
    Console.WriteLine("[Sort]");

    Array.Sort(arr);

    foreach (TimeSpan ts in arr) {
      Console.WriteLine(ts);
    }
    Console.WriteLine();
  }
}
実行結果
3.01:30:00
-01:30:00
00:00:01.5000000
00:00:03

[Sort]
-01:30:00
00:00:01.5000000
00:00:03
3.01:30:00

ソートについて詳しくは基本型のソートと昇順・降順でのソートをご覧ください。

§3.6 文字列への/からの変換

ToStringメソッドを用いることで、TimeSpanの表す時間間隔を文字列形式に変換することができます。 さらに、.NET Framework 4からは書式を指定して文字列化することが可能になっています。 これにより値をゼロ埋めしたり、時分秒などの区切り文字を変更して文字列化することができます。

using System;

class Sample {
  static void Main()
  {
    TimeSpan ts = new TimeSpan(1, 2, 34, 5, 678);

    Console.WriteLine(ts);
    Console.WriteLine(ts.ToString("g"));
    Console.WriteLine(ts.ToString("G"));
    Console.WriteLine("{0:g} {0:G}", ts);
    Console.WriteLine(ts.ToString(@"d' days 'h' hours 'm' minutes 's' seconds '"));
  }
}
実行結果
1.02:34:05.6780000
1:2:34:05.678
1:02:34:05.6780000
1:2:34:05.678 1:02:34:05.6780000
1 days 2 hours 34 minutes 5 seconds

TimeSpanに対して指定可能な書式については、書式指定子 §.時間間隔の書式指定子および書式指定子 §.時間間隔のカスタム書式指定子をご覧ください。

文字列からTimeSpanに変換するには、ParseメソッドTryParseメソッドを使うことが出来ます。 また、厳密に書式を指定して文字列からTimeSpanへ変換するには、ParseExactメソッドTryParseExactメソッドを使うことが出来ます。 なお、これらのメソッドはいずれも.NET Framework 4から使用可能になっています。

using System;

class Sample {
  static void Main()
  {
    string[] arr = new string[] {
      "1.2:34:5.678",           // 日数と時間の区切りに '.' を用いた形式
      "1:2:34:5.678",           // 日数と時間の区切りに ':' を用いた形式
      "1日2時間34分5秒678",     // 各区切り文字に日本語を用いた形式
      "01日02時間34分05秒678",
    };

    Console.WriteLine("[TryParse]");

    foreach (string s in arr) {
      // TryParseメソッドで変換を試みる
      TimeSpan ts;

      if (TimeSpan.TryParse(s, out ts))
        // 変換できた場合は結果を表示
        Console.WriteLine("{0} -> {1}", s, ts);
      else
        Console.WriteLine("{0} -> (invalid format)", s);
    }

    Console.WriteLine();

    Console.WriteLine("[TryParseExact]");

    foreach (string s in arr) {
      // TryParseExactメソッドで書式を指定して変換を試みる
      string[] formats = new[] {
        "G", // "一般的な形式"を表す書式指定子
        "d'日'h'時間'm'分's'秒'fff", // カスタム書式指定子の組み合わせ
      };

      TimeSpan ts;

      if (TimeSpan.TryParseExact(s, formats, null, out ts))
        // 変換できた場合は結果を表示
        Console.WriteLine("{0} -> {1}", s, ts);
      else
        Console.WriteLine("{0} -> (invalid format)", s);
    }
  }
}
実行結果
[TryParse]
1.2:34:5.678 -> 1.02:34:05.6780000
1:2:34:5.678 -> 1.02:34:05.6780000
1日2時間34分5秒678 -> (invalid format)
01日02時間34分05秒678 -> (invalid format)

[TryParseExact]
1.2:34:5.678 -> (invalid format)
1:2:34:5.678 -> 1.02:34:05.6780000
1日2時間34分5秒678 -> 1.02:34:05.6780000
01日02時間34分05秒678 -> 1.02:34:05.6780000

ToStringメソッドおよびParse*メソッドでは、現在のカルチャの影響を受けるもの(ローカライズされる書式)が存在します。 そのほか、これらのメソッドと文字列化に関する詳細な動作については日時・文字列の変換と書式でも解説しているので、あわせてご覧ください。