DateTime、DateOnly、DateTimeOffset、TimeSpan、TimeOnly、TimeZoneInfo の使い分け

.NET アプリケーションでは、複数の方法で日付と時刻の情報を使用できます。 日付と時刻の情報のより一般的な用途は、次のとおりです。

  • 日付のみを反映する (時刻情報は重要ではない)。
  • 時刻のみを反映する (日付情報は重要ではない)。
  • 特定の時刻と場所に関連付けられていない抽象日時を示す (たとえば、国際的チェーンではほとんどの店舗が平日午前 9:00 に開店します)。
  • .NET の外部ソースから日付と時刻の情報を取得する (通常、日付と時刻の情報は単純なデータ型に格納される)。
  • 単一の時点を一意かつ明確に識別する。 一部のアプリケーションでは、ホスト システムでのみ日付と時刻を明確にする必要があります。 その他のアプリでは、システム全体で明確にする必要があります (つまり、1 つのシステムでシリアル化された日付は有意に逆シリアル化され、世界中のどの場所においても別のシステムで使用されることがあります)。
  • 関連する複数の時刻を保持する (要求元の現地時刻やサーバーの Web 要求受信時刻など)。
  • 日付と時刻の演算を実行する (これにより、おそらく単一時点が一意かつ明確に識別される)。

.NET には DateTimeDateOnlyDateTimeOffsetTimeSpanTimeOnlyTimeZoneInfo の各型が含まれ、どれも日付と時刻を使用するアプリケーションを構築するために使用できます。

Note

この記事では、その機能が TimeZoneInfo クラスに完全に組み込まれている TimeZone については説明しません。 できる限り、TimeZone クラスの代わりに TimeZoneInfo クラスを使用してください。

DateTimeOffset 構造体

DateTimeOffset 構造体は、日付と時刻の値、およびその値と UTC との差異を示すオフセットを表します。 そのため、値は常に明確に単一時点を識別します。

DateTimeOffset 型には、 DateTime 型のすべての機能に加え、タイム ゾーンの処理機能が含まれます。 このため、次のことを実行するアプリケーションに適しています。

  • 単一の時点を一意かつ明確に識別する。 DateTimeOffset 型を使用して、「現在」の意味を明確に定義し、トランザクションの時刻を記録し、システム イベントまたはアプリケーション イベントの時刻を記録し、ファイル作成時刻とファイル変更時刻を記録することができます。
  • 一般的な日付と時刻の演算を実行する。
  • その時刻が 2 つの別々の値または構造体の 2 つのメンバーである場合、関連する複数の時刻を保持する。

Note

DateTimeOffset 値のこの用途は、 DateTime 値の用途と比べてはるかに一般的です。 その結果、DateTimeOffset をアプリケーション開発の既定の日付と時刻の型と見なしてください。

DateTimeOffset 値は特定のタイム ゾーンと関連付けられていませんが、さまざまなタイム ゾーンから派生することがあります。 いくつかの DateTimeOffset 値 (ローカルの太平洋標準時を含む) が属することがあるタイム ゾーンの一覧を次の例に示します。

using System;
using System.Collections.ObjectModel;

public class TimeOffsets
{
   public static void Main()
   {
      DateTime thisDate = new DateTime(2007, 3, 10, 0, 0, 0);
      DateTime dstDate = new DateTime(2007, 6, 10, 0, 0, 0);
      DateTimeOffset thisTime;

      thisTime = new DateTimeOffset(dstDate, new TimeSpan(-7, 0, 0));
      ShowPossibleTimeZones(thisTime);

      thisTime = new DateTimeOffset(thisDate, new TimeSpan(-6, 0, 0));
      ShowPossibleTimeZones(thisTime);

      thisTime = new DateTimeOffset(thisDate, new TimeSpan(+1, 0, 0));
      ShowPossibleTimeZones(thisTime);
   }

   private static void ShowPossibleTimeZones(DateTimeOffset offsetTime)
   {
      TimeSpan offset = offsetTime.Offset;
      ReadOnlyCollection<TimeZoneInfo> timeZones;

      Console.WriteLine("{0} could belong to the following time zones:",
                        offsetTime.ToString());
      // Get all time zones defined on local system
      timeZones = TimeZoneInfo.GetSystemTimeZones();
      // Iterate time zones
      foreach (TimeZoneInfo timeZone in timeZones)
      {
         // Compare offset with offset for that date in that time zone
         if (timeZone.GetUtcOffset(offsetTime.DateTime).Equals(offset))
            Console.WriteLine("   {0}", timeZone.DisplayName);
      }
      Console.WriteLine();
   }
}
// This example displays the following output to the console:
//       6/10/2007 12:00:00 AM -07:00 could belong to the following time zones:
//          (GMT-07:00) Arizona
//          (GMT-08:00) Pacific Time (US & Canada)
//          (GMT-08:00) Tijuana, Baja California
//
//       3/10/2007 12:00:00 AM -06:00 could belong to the following time zones:
//          (GMT-06:00) Central America
//          (GMT-06:00) Central Time (US & Canada)
//          (GMT-06:00) Guadalajara, Mexico City, Monterrey - New
//          (GMT-06:00) Guadalajara, Mexico City, Monterrey - Old
//          (GMT-06:00) Saskatchewan
//
//       3/10/2007 12:00:00 AM +01:00 could belong to the following time zones:
//          (GMT+01:00) Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna
//          (GMT+01:00) Belgrade, Bratislava, Budapest, Ljubljana, Prague
//          (GMT+01:00) Brussels, Copenhagen, Madrid, Paris
//          (GMT+01:00) Sarajevo, Skopje, Warsaw, Zagreb
//          (GMT+01:00) West Central Africa
Imports System.Collections.ObjectModel

Module TimeOffsets
    Public Sub Main()
        Dim thisTime As DateTimeOffset

        thisTime = New DateTimeOffset(#06/10/2007#, New TimeSpan(-7, 0, 0))
        ShowPossibleTimeZones(thisTime)

        thisTime = New DateTimeOffset(#03/10/2007#, New TimeSpan(-6, 0, 0))
        ShowPossibleTimeZones(thisTime)

        thisTime = New DateTimeOffset(#03/10/2007#, New TimeSpan(+1, 0, 0))
        ShowPossibleTimeZones(thisTime)
    End Sub

    Private Sub ShowPossibleTimeZones(offsetTime As DateTimeOffset)
        Dim offset As TimeSpan = offsetTime.Offset
        Dim timeZones As ReadOnlyCollection(Of TimeZoneInfo)

        Console.WriteLine("{0} could belong to the following time zones:", _
                          offsetTime.ToString())
        ' Get all time zones defined on local system
        timeZones = TimeZoneInfo.GetSystemTimeZones()
        ' Iterate time zones
        For Each timeZone As TimeZoneInfo In timeZones
            ' Compare offset with offset for that date in that time zone
            If timeZone.GetUtcOffset(offsetTime.DateTime).Equals(offset) Then
                Console.WriteLine("   {0}", timeZone.DisplayName)
            End If
        Next
        Console.WriteLine()
    End Sub
End Module
' This example displays the following output to the console:
'       6/10/2007 12:00:00 AM -07:00 could belong to the following time zones:
'          (GMT-07:00) Arizona
'          (GMT-08:00) Pacific Time (US & Canada)
'          (GMT-08:00) Tijuana, Baja California
'       
'       3/10/2007 12:00:00 AM -06:00 could belong to the following time zones:
'          (GMT-06:00) Central America
'          (GMT-06:00) Central Time (US & Canada)
'          (GMT-06:00) Guadalajara, Mexico City, Monterrey - New
'          (GMT-06:00) Guadalajara, Mexico City, Monterrey - Old
'          (GMT-06:00) Saskatchewan
'       
'       3/10/2007 12:00:00 AM +01:00 could belong to the following time zones:
'          (GMT+01:00) Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna
'          (GMT+01:00) Belgrade, Bratislava, Budapest, Ljubljana, Prague
'          (GMT+01:00) Brussels, Copenhagen, Madrid, Paris
'          (GMT+01:00) Sarajevo, Skopje, Warsaw, Zagreb
'          (GMT+01:00) West Central Africa

この例の日付と時刻の値はそれぞれ少なくとも 3 つの異なるタイム ゾーンに属することができることを出力は示しています。 DateTimeOffset 値の 6/10/2007 は、日時値が夏時間を表す場合、UTC のそのオフセットは必ずしも元のタイム ゾーンの基本 UTC オフセット、またはその表示名から見つかる UTC のオフセットと一致しないことを示しています。 単一の DateTimeOffset 値はそのタイム ゾーンと密接に関連していないため、夏時間との間のタイム ゾーンの遷移を反映させることができません。 これは、日付と時刻の演算を使用して DateTimeOffset 値を操作する際に問題となる可能性があります。 タイム ゾーンの調整規則を考慮に入れた日付と時刻の演算を実行する方法については、「日付と時刻を使用した算術演算の実行」を参照してください。

DateTime 構造体

DateTime 値は、特定の日付と時刻を定義します。 これには日付と時刻が属するタイム ゾーンに関する限られた情報を提供する Kind プロパティが含まれています。 DateTimeKind プロパティによって返される Kind 値は、 DateTime 値が現地時刻 (DateTimeKind.Local)、世界協定時刻 (UTC) (DateTimeKind.Utc)、指定されていない時刻 (DateTimeKind.Unspecified) のうちのどれを表すかを示します。

DateTime 構造は、次の 1 つ以上の特性を持つアプリケーションに適しています。

  • 抽象日時を使用するアプリケーション。
  • タイム ゾーン情報がない日時を使用するアプリケーション。
  • UTC 日時のみを使用するアプリケーション。
  • 日付と時刻の演算を実行しますが、これは一般的な結果に関するものです。 たとえば、6 カ月を特定の日付と時刻に加算する加算演算では、多くの場合、結果を夏時間に合わせて調整するかどうかは重要ではありません。

特定の DateTime 値が UTC を表さない場合、通常、その日付と時刻の値は、あいまいであるか、その移植性に限定されます。 たとえば、DateTime 値が現地時刻を表す場合、そのローカル タイム ゾーン内で移植することができます (つまり、値が同じタイム ゾーンの別のシステムで逆シリアル化される場合でも、その値は単一の時点を明確に指します)。 ローカル タイム ゾーン外では、その DateTime 値は複数の意味を持つ場合があります。 値の Kind プロパティが DateTimeKind.Unspecified の場合、移植性は低くなります。同じタイム ゾーン内であいまいになり、最初にシリアル化した同じシステムにおいてさえもあいまいになる可能性があります。 DateTime 値が UTC を表す場合のみ、値が使用されるシステムまたはタイム ゾーンに関係なく、その値は明確に単一時点を識別します。

重要

DateTime データを保存または共有する場合、UTC を使用し、DateTime 値の Kind プロパティを DateTimeKind.Utc に設定します。

DateOnly 構造体

DateOnly 構造体は、時間のない特定の日付を表します。 時間コンポーネントがないため、その日の始まりからその日の終わりまでの日付を表します。 この構造体は、生年月日、記念日、休日、ビジネス関連の日付など、特定の日付を格納するのに最適です。

時間コンポーネントを無視して DateTime を使用することもできますが、DateTime よりも DateOnly を使用する利点がいくつかあります。

  • DateTime 構造体は、タイム ゾーンによってオフセットされると、前または次の日にロールインする可能性があります。 DateOnly はタイム ゾーンでオフセットできず、常に設定された日付を表します。
  • DateTime 構造体のシリアル化には時間コンポーネントが含まれます。これは、データの意図をあいまいにする可能性があります。 また、DateOnly は、より少ないデータをシリアル化します。
  • コードが SQL Server などのデータベースとやり取りする場合、一般に日付全体が date データ型として格納され、時刻は含まれません。 DateOnly は、データベースの種類によりよく一致します。

DateOnly の詳細については、「DateOnly および TimeOnly 構造体の使用方法」を参照してください。

重要

DateOnlyは、.NET Framework では使用できません。

TimeSpan 構造体

TimeSpan 構造体は、時間間隔を表します。 その 2 つの一般的な用途は、次のとおりです。

  • 2 つの日付と時刻の値の間の時間を反映する。 たとえば、ある値から DateTime 値を減算すると、 TimeSpan 値が返されます。
  • 経過時間を測定する。 たとえば、Stopwatch.Elapsed プロパティでは、経過時間の測定を開始する Stopwatch メソッドの 1 つを呼び出してから経過した時間間隔を反映する、TimeSpan 値を返します。

値に特定の日付への参照がない時刻が反映される場合は、DateTime 値の代わりに TimeSpan 値を使用することもできます。 この用途は DateTime.TimeOfDay プロパティや DateTimeOffset.TimeOfDay プロパティと似ています。これらのプロパティでは、日付への参照なしの時間を表す TimeSpan 値を返します。 たとえば、 TimeSpan 構造体を使用して、ストアの開店時刻または閉店時刻を反映したり、標準イベントが発生したときの時刻を表したりするために使用できます。

以下の例では、開店時刻と閉店時刻用の StoreInfo オブジェクト、およびストアのタイム ゾーンを表す TimeSpan オブジェクトを含む TimeZoneInfo 構造体を定義します。 構造体には、 IsOpenNowIsOpenAtという 2 つのメソッドも含まれます。これは、ローカル タイム ゾーンにいると想定されるユーザーによって指定された時刻にストアがオープンするかどうかを示します。

using System;

public struct StoreInfo
{
   public String store;
   public TimeZoneInfo tz;
   public TimeSpan open;
   public TimeSpan close;

   public bool IsOpenNow()
   {
      return IsOpenAt(DateTime.Now.TimeOfDay);
   }

   public bool IsOpenAt(TimeSpan time)
   {
      TimeZoneInfo local = TimeZoneInfo.Local;
      TimeSpan offset = TimeZoneInfo.Local.BaseUtcOffset;

      // Is the store in the same time zone?
      if (tz.Equals(local)) {
         return time >= open & time <= close;
      }
      else {
         TimeSpan delta = TimeSpan.Zero;
         TimeSpan storeDelta = TimeSpan.Zero;

         // Is it daylight saving time in either time zone?
         if (local.IsDaylightSavingTime(DateTime.Now.Date + time))
            delta = local.GetAdjustmentRules()[local.GetAdjustmentRules().Length - 1].DaylightDelta;

         if (tz.IsDaylightSavingTime(TimeZoneInfo.ConvertTime(DateTime.Now.Date + time, local, tz)))
            storeDelta = tz.GetAdjustmentRules()[tz.GetAdjustmentRules().Length - 1].DaylightDelta;

         TimeSpan comparisonTime = time + (offset - tz.BaseUtcOffset).Negate() + (delta - storeDelta).Negate();
         return comparisonTime >= open & comparisonTime <= close;
      }
   }
}
Public Structure StoreInfo
    Dim store As String
    Dim tz As TimeZoneInfo
    Dim open As TimeSpan
    Dim close As TimeSpan

    Public Function IsOpenNow() As Boolean
        Return IsOpenAt(Date.Now.TimeOfDay)
    End Function

    Public Function IsOpenAt(time As TimeSpan) As Boolean
        Dim local As TimeZoneInfo = TimeZoneInfo.Local
        Dim offset As TimeSpan = TimeZoneInfo.Local.BaseUtcOffset

        ' Is the store in the same time zone?
        If tz.Equals(local) Then
            Return time >= open AndAlso time <= close
        Else
            Dim delta As TimeSpan = TimeSpan.Zero
            Dim storeDelta As TimeSpan = TimeSpan.Zero

            ' Is it daylight saving time in either time zone?
            If local.IsDaylightSavingTime(Date.Now.Date + time) Then
                delta = local.GetAdjustmentRules(local.GetAdjustmentRules().Length - 1).DaylightDelta
            End If
            If tz.IsDaylightSavingTime(TimeZoneInfo.ConvertTime(Date.Now.Date + time, local, tz))
                storeDelta = tz.GetAdjustmentRules(tz.GetAdjustmentRules().Length - 1).DaylightDelta
            End If
            Dim comparisonTime As TimeSpan = time + (offset - tz.BaseUtcOffset).Negate() + (delta - storeDelta).Negate
            Return (comparisonTime >= open AndAlso comparisonTime <= close)
        End If
    End Function
End Structure

StoreInfo 構造体をクライアント コードで次のように使用できます。

public class Example
{
   public static void Main()
   {
      // Instantiate a StoreInfo object.
      var store103 = new StoreInfo();
      store103.store = "Store #103";
      store103.tz = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time");
      // Store opens at 8:00.
      store103.open = new TimeSpan(8, 0, 0);
      // Store closes at 9:30.
      store103.close = new TimeSpan(21, 30, 0);

      Console.WriteLine("Store is open now at {0}: {1}",
                        DateTime.Now.TimeOfDay, store103.IsOpenNow());
      TimeSpan[] times = { new TimeSpan(8, 0, 0), new TimeSpan(21, 0, 0),
                           new TimeSpan(4, 59, 0), new TimeSpan(18, 31, 0) };
      foreach (var time in times)
         Console.WriteLine("Store is open at {0}: {1}",
                           time, store103.IsOpenAt(time));
   }
}
// The example displays the following output:
//       Store is open now at 15:29:01.6129911: True
//       Store is open at 08:00:00: True
//       Store is open at 21:00:00: False
//       Store is open at 04:59:00: False
//       Store is open at 18:31:00: False
Module Example
    Public Sub Main()
        ' Instantiate a StoreInfo object.
        Dim store103 As New StoreInfo()
        store103.store = "Store #103"
        store103.tz = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time")
        ' Store opens at 8:00.
        store103.open = new TimeSpan(8, 0, 0)
        ' Store closes at 9:30.
        store103.close = new TimeSpan(21, 30, 0)

        Console.WriteLine("Store is open now at {0}: {1}",
                          Date.Now.TimeOfDay, store103.IsOpenNow())
        Dim times() As TimeSpan = {New TimeSpan(8, 0, 0),
                                    New TimeSpan(21, 0, 0),
                                    New TimeSpan(4, 59, 0),
                                    New TimeSpan(18, 31, 0)}
        For Each time In times
            Console.WriteLine("Store is open at {0}: {1}",
                              time, store103.IsOpenAt(time))
        Next
    End Sub
End Module
' The example displays the following output:
'       Store is open now at 15:29:01.6129911: True
'       Store is open at 08:00:00: True
'       Store is open at 21:00:00: False
'       Store is open at 04:59:00: False
'       Store is open at 18:31:00: False

TimeOnly 構造体

TimeOnly 構造体は、目覚まし時計や毎日の昼食時間など、時刻の値を表します。 TimeOnly は、特定の時刻である 00:00:00.0000000 - 23:59:59.9999999 の範囲に制限されます。

TimeOnly 型が導入される前は、プログラマは通常、DateTime 型または TimeSpan 型を使用して特定の時刻を表しました。 ただし、これらの構造体を使用して日付のない時刻をシミュレートすると、次のような問題が発生する可能性がありますが、TimeOnly はこれを解決します。

  • TimeSpan は、ストップウォッチで測定された時間など、経過時間を表します。 上限の範囲は 29,000 年を超え、その値を負にして、時間の逆行を示すことができます。 負の TimeSpan は、特定の時刻を示すわけではありません。
  • TimeSpan が時刻として使用されている場合は、24 時間外の値に操作される可能性があります。 TimeOnly にはこのリスクがありません。 たとえば、従業員の勤務シフトが 18:00 から始まり、8 時間続く場合、TimeOnly 構造体に 8 時間を加算すると、2:00 にロールオーバーされます。
  • 時刻に DateTime を使用するには、任意の日付を時刻に関連付けた後、無視する必要があります。 日付として DateTime.MinValue (0001-01-01) を選択するのが一般的ですが、DateTime 値から時間を減算すると、OutOfRange 例外が発生する可能性があります。 TimeOnly では、24 時間の時間枠で循環して時刻が進んだり戻ったりするので、この問題はありません。
  • DateTime 構造体のシリアル化には日付コンポーネントが含まれます。これは、データの意図をあいまいにする可能性があります。 また、TimeOnly は、より少ないデータをシリアル化します。

TimeOnly の詳細については、「DateOnly および TimeOnly 構造体の使用方法」を参照してください。

重要

TimeOnly は、.NET Framework では使用できません。

TimeZoneInfo クラス

TimeZoneInfo class represents any of the Earth's time zones, and enables the conversion of any date and time in one time zone to its equivalent in another time zone. TimeZoneInfo クラスにより、日付と時刻を使用して、どの日付と時刻の値も明確に単一時点を識別できるようにすることができます。 TimeZoneInfo クラスを拡張することもできます。 Windows システムで提供され、レジストリで定義されているタイム ゾーン情報に依存していますが、カスタムのタイム ゾーンの作成もサポートされています。 また、タイム ゾーン情報のシリアル化と逆シリアル化もサポートされています。

場合によっては、 TimeZoneInfo クラスをフル活用するために、開発作業をさらに実行する必要が生じることもあります。 日付と時刻の値が、それらが属するタイム ゾーンに密接に関連付けられていない場合は、追加の作業が必要です。 アプリケーションによって日付と時刻をその関連タイム ゾーンとリンクするメカニズムが提供されていない場合、特定の日時値とそのタイム ゾーンの関連付けは簡単に解除されます。 この情報をリンクする 1 つの方法は、日付と時刻の値とその関連タイム ゾーン オブジェクトの両方を含むクラスまたは構造体を定義するという方法です。

.NET のタイム ゾーンのサポートを利用するには、日付と時刻のオブジェクトがインスタンスされるときに、その日時値が属するタイム ゾーンを把握している必要があります。 タイム ゾーンは、特に Web アプリやネットワーク アプリでは、多くのケースで不明です。

関連項目