Выбор между DateTime, DateOnly, DateTimeOffset, TimeSpan, TimeOnly и TimeZoneInfo

Приложения .NET могут использовать сведения о дате и времени несколькими способами. Ниже приведены наиболее распространенные способы использования сведений о дате и времени.

  • для представления только даты; сведения о времени не имеют значения;
  • для представления только времени; сведения о дате не имеют значения;
  • Чтобы отразить абстрактную дату и время, которое не привязано к определенному времени и месту (например, большинство магазинов в международной сети открываются в будничных днях в 9:00 утра).
  • Для получения сведений о дате и времени из источников за пределами .NET обычно сведения о дате и времени хранятся в простом типе данных.
  • для однозначной идентификации конкретного момента времени: Для некоторых приложений требуется, чтобы дата и время были однозначно указать только в системе узла. Для других приложений требуется однозначное использование в разных системах (т. е. сериализованная дата в одной системе может быть значимо десериализирована и использована в другой системе в любом месте мира).
  • Чтобы сохранить несколько связанных раз (например, локальное время запрашивающего и время получения сервера для веб-запроса).
  • для выполнения арифметических операций с датой и временем, возможно, с результатом, однозначно определяющим момент времени.

.NET включает в себя DateTime, DateOnly, DateTimeOffset, TimeOnlyTimeSpanи типы, все из которых можно использовать для создания приложений, работающих с датами и TimeZoneInfo временем.

Примечание.

Эта статья не обсуждается TimeZone , так как ее функциональные возможности практически полностью включены в TimeZoneInfo класс. По возможности используйте TimeZoneInfo класс вместо TimeZone класса.

Структура DateTimeOffset

Структура DateTimeOffset представляет значение даты и времени, а также смещение, которое указывает, насколько это значение отличается от времени в формате UTC. Таким образом, значение всегда однозначно идентифицирует единственный момент времени.

Тип DateTimeOffset включает все функциональные возможности типа DateTime , а также сведения о часовом поясе. Это делает его подходящим для приложений, которые:

  • однозначно идентифицируют единственный момент времени; тип DateTimeOffset можно использовать для однозначного определения текущего момента времени, для ведения журнала времени транзакций, ведения журнала времени событий системы или приложений, а также для записи времени создания и изменения файлов;
  • выполняют общие арифметические операции с датами и временем;
  • сохраняют несколько связанных значений времени, если они хранятся как два отдельных значения или два члена структуры.

Примечание.

Эти варианты использования значений 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;
  • выполняют арифметические операции с датой и временем, но учитывают только общие результаты. Например, в операции сложения, которая добавляет шесть месяцев к определенным дате и времени, часто не важно, корректируется ли результат с учетом перехода на летнее время.

Кроме случая, когда определенное значение DateTime представляет время в формате UTC, значение даты и времени часто является неоднозначным или ограниченным в плане возможности переноса. Например, если DateTime значение представляет локальное время, оно переносимо в пределах этого локального часового пояса (то есть, если значение десериализировано в другой системе в том же часовом поясе, это значение по-прежнему однозначно идентифицирует один момент времени). За пределами местного часового пояса значение DateTime может иметь несколько интерпретаций. Если свойство DateTimeKind.Unspecifiedзначения Kind имеет значение, оно еще менее переносимо: в настоящее время неоднозначно в пределах одного часового пояса и, возможно, даже в той же системе, где она была впервые сериализована. Значение DateTime однозначно идентифицирует момент времени независимо от времени системы или часового пояса, в котором оно используется, только если это значение представляет время в формате UTC.

Внимание

При сохранении или совместном использовании DateTime данных используйте utc и задайте DateTime для свойства DateTimeKind.Utcзначения Kind значение .

Структура DateOnly

Структура DateOnly представляет определенную дату без времени. Поскольку он не имеет компонента времени, он представляет дату с начала дня до конца дня. Эта структура идеально подходит для хранения конкретных дат, таких как дата рождения, дата годовщины, праздник или дата, связанная с бизнесом.

Несмотря на то, что вы можете использовать DateTime при игнорировать компонент времени, существует несколько преимуществ использованияDateOnly:DateTime

  • Структура DateTime может свернуться в предыдущий или следующий день, если это смещение часового пояса. DateOnly не может быть смещение по часовой поясу, и всегда представляет дату, заданную.
  • Сериализация DateTime структуры включает компонент времени, который может скрыть намерение данных. Кроме того, DateOnly сериализует меньше данных.
  • При взаимодействии кода с базой данных, например SQL Server, все даты обычно хранятся как date тип данных, который не включает время. DateOnly лучше соответствует типу базы данных.

Дополнительные сведения см. в DateOnlyразделе "Использование структур DateOnly и TimeOnly".

Внимание

DateOnlyнедоступно в платформа .NET Framework.

Структура TimeSpan

Структура TimeSpan представляет интервал времени. Ее обычно используют двумя способами:

  • для отражения интервала времени между двумя значениями даты и времени (например, при вычитании одного значения DateTime из другого возвращается значение TimeSpan );
  • для измерения прошедшего времени. Например, Stopwatch.Elapsed свойство возвращает TimeSpan значение, которое отражает интервал времени, прошедший с момента вызова одного из Stopwatch методов, начинающих измерять истекшее время.

Значение TimeSpan также можно использовать в качестве замены DateTime значения, если это значение отражает время без ссылки на определенный день. Это использование аналогично DateTime.TimeOfDay свойствам и DateTimeOffset.TimeOfDay возвращает TimeSpan значение, представляющее время без ссылки на дату. Например, структуру TimeSpan можно использовать для представления ежедневного времени открытия или закрытия магазина или времени, в которое происходит любое регулярное событие.

В примере ниже определяется структура StoreInfo , которая включает объекты TimeSpan , представляющие время закрытия и открытия магазина, а также объект TimeZoneInfo , представляющий часовой пояс магазина. Структура также включает два метода, IsOpenNow и IsOpenAt, указывающие, открыт ли магазин в то время, которое указал пользователь, предположительно находящийся в местном часовом поясе.

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.9999999999, определенное время дня.

TimeOnly До введения типа программисты обычно используют DateTime тип или TimeSpan тип для представления определенного времени. Однако использование этих структур для имитации времени без даты может привести к некоторым проблемам, которые TimeOnly решают:

  • TimeSpan представляет истекшее время, например время, измеряемое с помощью стоп-часов. Верхний диапазон составляет более 29 000 лет, и его значение может быть отрицательным, чтобы указать, что перемещение назад во времени. Отрицательный TimeSpan не указывает определенное время дня.
  • Если TimeSpan используется в качестве времени суток, существует риск того, что он может быть манипулировать значением за пределами 24-часового дня. TimeOnly не имеет этого риска. Например, если смена работы сотрудника начинается с 18:00 и длится в течение 8 часов, добавление 8 часов в TimeOnly структуру выполняется до 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 может потребовать дальнейших усилий по разработке. Если значения даты и времени не тесно связаны с часовыми поясами, к которым они относятся, требуется дополнительная работа. Если приложение не предоставляет некоторые механизмы связывания даты и времени со связанным часовым поясом, это легко для определенного значения даты и времени, чтобы быть отсоединены от часового пояса. Одним из способов связывания этой информации является определение класса или структуры, содержащих значение даты и времени и связанный с ним часовой пояс.

Чтобы воспользоваться поддержкой часового пояса в .NET, необходимо знать часовой пояс, к которому относится значение даты и времени при создании экземпляра объекта даты и времени. Часовой пояс часто не известен, особенно в веб-приложениях или сетевых приложениях.

См. также