DateTime, DateOnly, DateTimeOffset, TimeSpan, TimeOnly, TimeZoneInfo 중에서 선택

.NET 애플리케이션은 여러 가지 방법으로 날짜 및 시간 정보를 사용할 수 있습니다. 날짜 및 시간 정보의 더 일반적인 용도는 다음과 같습니다.

  • 시간 정보가 중요하지 않도록 날짜만 반영합니다.
  • 날짜 정보가 중요하지 않도록 시간만 반영합니다.
  • 특정 시간과 장소에 연결되지 않은 추상 날짜와 시간을 반영합니다(예: 국제 체인의 상점은 대부분 평일 오전 9:00에 열림).
  • 일반적으로 날짜 및 시간 정보가 단순 데이터 형식으로 저장되는 .NET 외부 원본에서 날짜 및 시간 정보를 검색합니다.
  • 단일 시점을 고유하고 명확하게 식별합니다. 일부 애플리케이션에서는 호스트 시스템에서만 날짜와 시간이 명확해야 합니다. 다른 앱에서는 시스템 간에 모호하지 않아야 합니다(즉, 한 시스템에서 직렬화된 날짜가 전 세계 어디에서나 다른 시스템에서 의미 있게 역직렬화되어 사용될 수 있어야 함).
  • 여러 관련 시간(예: 요청자의 현지 시간 및 서버의 웹 요청 수신 시간)을 보존합니다.
  • 날짜 및 시간 산술 연산을 수행합니다(단일 시점을 고유하고 명확하게 식별하는 결과를 제공할 수 있음).

.NET에는 DateTime, DateOnly, DateTimeOffset, TimeSpan, TimeOnly, TimeZoneInfo 형식이 포함되어 있으며, 모두 날짜 및 시간으로 작동하는 애플리케이션을 빌드하는 데 사용할 수 있습니다.

참고 항목

이 문서에서는 TimeZone에 대해 설명하지 않습니다. 해당 기능이 TimeZoneInfo 클래스에 거의 완전히 통합되어 있기 때문입니다. 가능하면 TimeZone 클래스 대신 TimeZoneInfo 클래스를 사용하세요.

DateTimeOffset 구조체

DateTimeOffset 구조체는 날짜 및 시간 값과 해당 값이 UTC와 다른 정도를 나타내는 오프셋을 나타냅니다. 따라서 값이 항상 단일 시점을 명확하게 식별합니다.

DateTimeOffset 형식에는 DateTime 형식의 모든 기능과 표준 시간대 인식 기능이 포함됩니다. 따라서 다음과 같은 애플리케이션에 적합합니다.

  • 단일 시점을 고유하고 명확하게 식별합니다. DateTimeOffset 형식을 통해 “now"의 의미를 명확하게 정의하고, 트랜잭션 시간을 기록하고, 시스템 또는 애플리케이션 이벤트의 시간을 기록하고, 파일을 만든 시간과 수정 시간을 기록할 수 있습니다.
  • 일반적인 날짜 및 시간 산술 연산을 수행합니다.
  • 시간이 두 개의 개별 값으로 저장되거나 한 구조체의 두 멤버로 저장된 경우 관련된 여러 시간을 보존합니다.

참고 항목

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

출력은 이 예제의 각 날짜 및 시간 값이 세 개 이상의 표준 시간대에 속할 수 있음을 보여 줍니다. DateTimeOffset 값인 6/10/2007은 날짜 및 시간 값이 일광 절약 시간제를 나타내는 경우 UTC로부터의 오프셋이 원래 표준 시간대의 기본 UTC 오프셋이나 표시 이름에 있는 UTC로부터의 오프셋과 반드시 일치하지 않는다는 것을 보여 줍니다. 단일 DateTimeOffset 값은 해당 표준 시간대와 밀접하게 연결되어 있지 않기 때문에 일광 절약 시간제에 따른 표준 시간대의 전환을 반영할 수 없습니다. 날짜 및 시간 산술을 사용하여 DateTimeOffset 값을 조작하는 경우 이러한 문제가 발생할 수 있습니다. 표준 시간대의 조정 규칙을 고려하는 방식으로 날짜 및 시간 산술 연산을 수행하는 방법에 대한 자세한 내용은 날짜 및 시간으로 산술 연산 수행을 참조하십시오.

DateTime 구조체

DateTime 값은 특정 날짜와 시간을 정의합니다. 여기에는 해당 날짜와 시간이 속한 시간대에 대한 제한된 정보를 제공하는 Kind 속성이 포함되어 있습니다. DateTimeKind 값은 Kind 속성에서 반환된 값은 DateTime 값이 현지 시간(DateTimeKind.Local), UTC(협정 세계시)(DateTimeKind.Utc) 또는 지정되지 않은 시간(DateTimeKind.Unspecified)인지를 나타냅니다.

DateTime 구조는 다음 특성 중 하나 이상을 가진 애플리케이션에 적합합니다.

  • 추상 날짜와 시간을 사용합니다.
  • 표준 시간대 정보가 없는 날짜 및 시간을 사용합니다.
  • UTC 날짜 및 시간만 사용합니다.
  • 날짜 및 시간 산술 연산을 수행하지만 일반적인 결과와 관련이 있습니다. 예를 들어 특정 날짜와 시간에 6개월을 더하는 더하기 연산에서 일광 절약 시간제에 맞게 결과를 조정하는지 여부는 대체로 중요하지 않습니다.

특정 DateTime 값이 UTC를 나타내지 않는 경우 해당 날짜 및 시간 값은 대체로 모호하거나 이식성이 제한됩니다. 예를 들어 DateTime 값이 현지 시간을 나타내는 경우 현지 표준 시간대 내에서 이식할 수 있습니다(즉, 동일한 표준 시간대의 다른 시스템에서 값을 역직렬화하는 경우 해당 값이 여전히 단일 시점을 명확하게 식별함). 현지 표준 시간대 외부에서는 해당 DateTime 값이 여러 가지로 해석될 수 있습니다. 값의 Kind 속성이 DateTimeKind.Unspecified이면 이식성이 훨씬 감소합니다. 이제 동일한 표준 시간대 내에서 모호하며 처음 직렬화된 시스템에서도 모호할 수 있습니다. DateTime 값이 UTC를 나타내는 경우에만 값이 사용되는 시스템이나 표준 시간대에 관계없이 단일 시점을 명확하게 식별합니다.

Important

DateTime 데이터를 저장하거나 공유할 때 UTC를 사용하고 DateTime 값의 Kind 속성을 DateTimeKind.Utc로 설정합니다.

DateOnly 구조체

DateOnly 구조체는 시간 없는 특정 날짜를 나타냅니다. 시간 구성 요소가 없으므로 하루의 시작부터 종료까지의 날짜를 나타냅니다. 이 구조체는 생년월일, 기념일, 휴일 또는 비즈니스 관련 날짜 등의 특정 날짜를 저장하는 데 적합합니다.

시간 구성 요소를 무시하면서 DateTime을 사용할 수 있지만 DateOnly를 사용할 경우 DateTime보다 몇 가지 이점이 있습니다.

  • DateTime 구조체는 표준 시간대로 오프셋된 경우 이전 또는 다음 날로 롤링될 수 있습니다. DateOnly는 표준 시간대로 오프셋할 수 없으며 항상 설정된 날짜를 나타냅니다.
  • DateTime 구조체를 직렬화하면 데이터의 의도를 모호하게 할 수 있는 시간 구성 요소가 포함됩니다. 또한 DateOnly는 더 적은 데이터를 직렬화합니다.
  • 코드가 SQL Server와 같은 데이터베이스와 상호 작용하는 경우 전체 날짜는 일반적으로 시간을 포함하지 않는 date 데이터 형식으로 저장됩니다. DateOnly는 데이터베이스 형식과 더 잘 일치합니다.

DateOnly에 대한 자세한 내용은 DateOnly 및 TimeOnly 구조를 사용하는 방법을 참조하세요.

Important

DateOnly는 .NET Framework에서 사용할 수 없습니다.

TimeSpan 구조체

TimeSpan 구조체는 시간 간격을 나타냅니다. 일반적으로 다음 두 가지 용도로 사용됩니다.

  • 두 개의 날짜 및 시간 값의 시간 간격을 반영합니다. 예를 들어 다른 값에서 DateTime 값을 빼면 TimeSpan 값이 반환됩니다.
  • 경과 시간을 측정합니다. 예를 들어 Stopwatch.Elapsed 속성은 경과 시간 측정을 시작하는 Stopwatch 메서드 중 하나의 호출 이후에 경과된 시간 간격을 반영하는 TimeSpan 값을 반환합니다.

값이 특정 하루를 참조하지 않고 시간을 반영하는 경우 TimeSpan 값을 DateTime 값 대신 사용할 수도 있습니다. 이 사용은 날짜를 참조하지 않고 시간을 나타내는 TimeSpan 값을 반환하는 DateTime.TimeOfDayDateTimeOffset.TimeOfDay 속성과 유사합니다. 예를 들어 TimeSpan 구조체를 사용하여 매일 상점을 여는 시간 또는 닫는 시간을 반영하거나 정기 이벤트가 발생하는 시간을 나타낼 수 있습니다.

다음 예제에서는 상점을 여는 시간과 닫는 시간에 사용되는 StoreInfo 개체와 상점의 표준 시간대를 나타내는 TimeSpan 개체가 포함된 TimeZoneInfo 구조체를 정의합니다. 구조체에는 현지 표준 시간대에 있는 것으로 가정되는 사용자가 지정한 시간에 상점이 열려 있는지 여부를 나타내는 IsOpenNowIsOpenAt의 두 메서드도 포함됩니다.

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 예외가 발생할 수 있습니다. 시간이 24시간 범위로 앞뒤로 롤링되므로 TimeOnly에서 이 문제가 발생하지 않습니다.
  • DateTime 구조체를 직렬화하면 날짜 구성 요소가 포함되며 이로 인해 데이터의 의도가 모호해질 수 있습니다. 또한 TimeOnly는 더 적은 데이터를 직렬화합니다.

TimeOnly에 대한 자세한 내용은 DateOnly 및 TimeOnly 구조를 사용하는 방법을 참조하세요.

Important

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 클래스를 완전히 활용하기 위해 추가 개발 작업이 필요한 경우도 있습니다. 날짜 및 시간 값이 해당 날짜 및 시간대가 속한 표준 시간대와 긴밀하게 연결되어 있지 않은 경우 추가 작업이 필요합니다. 애플리케이션이 날짜 및 시간을 관련 표준 시간대에 연결하는 메커니즘을 제공하지 않을 경우 특정 날짜 및 시간 값과 표준 시간대의 연결이 쉽게 끊어집니다. 이 정보를 연결하는 한 가지 방법으로 날짜 및 시간 값, 그리고 연결되는 표준 시간대 개체를 둘 다 포함하는 클래스나 구조체를 정의합니다.

.NET에서 표준 시간대 지원을 활용하려면 날짜 및 시간 개체가 인스턴스화될 때 해당 날짜 및 시간 값이 속하는 표준 시간대를 알아야 합니다. 특히 웹 또는 네트워크 앱에서는 표준 시간대를 알 수 없는 경우가 많습니다.

참고 항목