Выбор между DateTime, DateOnly, DateTimeOffset, TimeSpan, TimeOnly и TimeZoneInfo
Приложения .NET могут использовать сведения о дате и времени несколькими способами. Ниже приведены наиболее распространенные способы использования сведений о дате и времени.
- Для отражения только даты, поэтому сведения о времени не важны.
- Чтобы отразить только время, поэтому сведения о дате не важны.
- Чтобы отразить абстрактную дату и время, которое не привязано к определенному времени и месту (например, большинство магазинов в международной сети открываются в будничных днях в 9:00 утра).
- Для получения сведений о дате и времени из источников за пределами .NET обычно сведения о дате и времени хранятся в простом типе данных.
- Чтобы однозначно идентифицировать один момент времени. Для некоторых приложений требуется, чтобы дата и время были однозначно определены только на системе узла. Для других приложений требуется, чтобы дата была однозначно интерпретируема в разных системах (т. е. дата, сериализованная в одной системе, может быть правильно десериализирована и использована в другой системе где угодно в мире).
- Чтобы сохранить несколько связанных временных показателей (например, локальное время запрашивающего и время получения на сервере для веб-запроса).
- Для выполнения арифметики даты и времени, возможно, с результатом, который однозначно идентифицирует одну точку во времени.
.NET включает DateTime, DateOnly, DateTimeOffset, TimeSpan, TimeOnlyи типы TimeZoneInfo, все из которых можно использовать для создания приложений, работающих с датами и временем.
Заметка
Эта статья не обсуждает TimeZone, так как ее функциональные возможности практически полностью включены в класс TimeZoneInfo. По возможности используйте класс TimeZoneInfo вместо класса TimeZone.
Структура 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.
- Выполняйте арифметику даты и времени, но сосредоточьтесь на общих результатах. Например, в операции добавления, которая добавляет шесть месяцев к определенной дате и времени, часто не важно, корректируется ли результат для летнего времени.
Если конкретное значение DateTime не представляет UTC, то значение даты и времени часто неоднозначно или ограничено в его переносимости. Например, если значение DateTime представляет локальное время, оно переносимо в пределах этого локального часового пояса (то есть, если значение десериализировано в другой системе в том же часовом поясе, это значение по-прежнему однозначно идентифицирует один момент времени). Вне локального часового пояса значение DateTime может иметь несколько интерпретаций. Если свойство Kind значения DateTimeKind.Unspecified, оно еще менее переносимо: теперь оно неоднозначно в пределах одного часового пояса и, возможно, даже в той же системе, где она была впервые сериализована. Только если значение DateTime представляет utc, это значение однозначно определяет один момент времени независимо от системы или часового пояса, в котором используется значение.
Важный
При сохранении или совместном использовании данных DateTime используйте utc и задайте для свойства DateTime значения Kind значение DateTimeKind.Utc.
Структура 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: True
// Store is open at 04:59:00: False
// Store is open at 18:31:00: True
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 часов, добавление 8 часов в структуруTimeOnly
перемещается на 2:00. - Использование
DateTime
для времени суток требует, чтобы произвольная дата была связана с временем, а затем позже игнорировалась. Обычно рекомендуется выбратьDateTime.MinValue
(0001-01-01) в качестве даты, однако если часы вычитаются из значенияDateTime
, может возникнуть исключениеOutOfRange
.TimeOnly
не имеет этой проблемы, так как время движется вперёд и назад в пределах 24 часов. - Сериализация структуры
DateTime
включает компонент даты, который может скрыть намерение данных. Кроме того,TimeOnly
сериализует меньше данных.
Дополнительные сведения о TimeOnly
см. в разделе Использование структур DateOnly и TimeOnly.
Важный
TimeOnly
недоступно для .NET Framework.
Класс TimeZoneInfo
Класс TimeZoneInfo представляет любой из часовых поясов Земли и позволяет преобразовать любую дату и время в один часовой пояс в его эквивалент в другом часовом поясе. Класс TimeZoneInfo позволяет работать с датами и временем, чтобы любое значение даты и времени однозначно идентифицирует одну точку во времени. Класс TimeZoneInfo также расширяемый. Хотя это зависит от информации часового пояса, предоставленной для систем Windows и определенных в реестре, она поддерживает создание пользовательских часовых поясов. Она также поддерживает сериализацию и десериализацию сведений часового пояса.
В некоторых случаях для использования класса TimeZoneInfo может потребоваться дальнейшая работа по разработке. Если значения даты и времени не тесно связаны с часовыми поясами, к которым они относятся, требуется дополнительная работа. Если ваше приложение не предоставляет механизма для связывания даты и времени с соответствующим часовым поясом, легко потерять ассоциацию значения даты и времени с его часовым поясом. Одним из способов связывания этих сведений является определение класса или структуры, содержащей значение даты и времени и связанный с ним объект часового пояса.
Чтобы воспользоваться поддержкой часового пояса в .NET, необходимо знать, к какому часовому поясу относятся дата и время при создании объекта даты и времени. Часовой пояс часто не известен, особенно в веб-приложениях или сетевых приложениях.