Выбор между типами DateTime, DateTimeOffset, TimeSpan и TimeZoneInfo

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

  • для представления только даты; сведения о времени не имеют значения;

  • для представления только времени; сведения о дате не имеют значения;

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

  • Для получения сведений о дате и времени из источников, находящихся за пределами .NET, обычно данные о дате и времени хранятся в простом типе данных.

  • для однозначной идентификации конкретного момента времени: Некоторым приложениям требуется, чтобы дата и время были однозначными только в основной системе. Для других приложений требуется однозначная десериализация в разных системах (т. е. Дата, сериализованная на одной системе, может быть понятна и использована в другой системе в любой точке мира).

  • для сохранения нескольких связанных значений времени (например, местного времени запрашивающей системы и серверного времени получения веб-запроса);

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

.NET включает DateTime типы, DateTimeOffset , TimeSpan и 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;

  • Получение сведений о дате и времени из источников за пределами .NET, таких как базы данных SQL. (как правило, информация о дате и времени хранится в этих источниках в простом формате, который совместим со структурой DateTime );

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

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

Важно!

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

Структура 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

Класс 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, необходимо знание часового пояса, к которому принадлежит значение даты и времени при создании экземпляра объекта даты и времени. Часовой пояс часто неизвестен, особенно в веб-или сетевых приложениях.

См. также статью