Практическое руководство. Использование часовых поясов в арифметических операциях с датами и временем
Обычно при выполнении арифметики даты и времени с использованием DateTime или DateTimeOffset значениями результат не отражает правила корректировки часового пояса. Это верно, даже если часовой пояс значения даты и времени четко идентифицируется (например, если Kind свойству присвоено значение Local). В этом разделе показано, как выполнять арифметические операции с значениями даты и времени, принадлежащими определенному часовому поясу. Результаты арифметических операций при этом будут учитывать правила коррекции часовых поясов.
Применение правил коррекции в вычислениях с датами и временем
Необходимо каким-либо способом тесно связать значения даты и времени с соответствующим часовым поясом. К примеру, можно объявить структуру, которая будет содержать значение даты и времени вместе с данными о часовом поясе. В следующем примере используется этот подход для связывания DateTime значения с часовыми поясами.
// Define a structure for DateTime values for internal use only internal struct TimeWithTimeZone { TimeZoneInfo TimeZone; DateTime Time; }
' Define a structure for DateTime values for internal use only Friend Structure TimeWithTimeZone Dim TimeZone As TimeZoneInfo Dim Time As Date End Structure
Преобразуйте время в универсальное время (UTC), вызвав ConvertTimeToUtc метод или ConvertTime метод.
Далее следует выполнить необходимые арифметические действия над временем UTC.
Преобразуйте время из UTC в связанный часовой пояс исходного времени путем вызова TimeZoneInfo.ConvertTime(DateTime, TimeZoneInfo) метода.
Пример
В следующем примере добавляется два часа и тридцать минут к 9 марта 2008 г., в 1:30 утра Центральное стандартное время. Переход часового пояса на летнее время происходит тридцати минут спустя, в 2:00 утра 9 марта 2008 года. Так как в примере следуют четыре шага, перечисленные в предыдущем разделе, оно правильно сообщает о результирующем времени как 5:00 утра 9 марта 2008 года.
using System;
public struct TimeZoneTime
{
public TimeZoneInfo TimeZone;
public DateTime Time;
public TimeZoneTime(TimeZoneInfo tz, DateTime time)
{
if (tz == null)
throw new ArgumentNullException("The time zone cannot be a null reference.");
this.TimeZone = tz;
this.Time = time;
}
public TimeZoneTime AddTime(TimeSpan interval)
{
// Convert time to UTC
DateTime utcTime = TimeZoneInfo.ConvertTimeToUtc(this.Time, this.TimeZone);
// Add time interval to time
utcTime = utcTime.Add(interval);
// Convert time back to time in time zone
return new TimeZoneTime(this.TimeZone, TimeZoneInfo.ConvertTime(utcTime,
TimeZoneInfo.Utc, this.TimeZone));
}
}
public class TimeArithmetic
{
public const string tzName = "Central Standard Time";
public static void Main()
{
try
{
TimeZoneTime cstTime1, cstTime2;
TimeZoneInfo cst = TimeZoneInfo.FindSystemTimeZoneById(tzName);
DateTime time1 = new DateTime(2008, 3, 9, 1, 30, 0);
TimeSpan twoAndAHalfHours = new TimeSpan(2, 30, 0);
cstTime1 = new TimeZoneTime(cst, time1);
cstTime2 = cstTime1.AddTime(twoAndAHalfHours);
Console.WriteLine("{0} + {1} hours = {2}", cstTime1.Time,
twoAndAHalfHours.ToString(),
cstTime2.Time);
}
catch
{
Console.WriteLine("Unable to find {0}.", tzName);
}
}
}
Public Structure TimeZoneTime
Public TimeZone As TimeZoneInfo
Public Time As Date
Public Sub New(tz As TimeZoneInfo, time As Date)
If tz Is Nothing Then _
Throw New ArgumentNullException("The time zone cannot be a null reference.")
Me.TimeZone = tz
Me.Time = time
End Sub
Public Function AddTime(interval As TimeSpan) As TimeZoneTime
' Convert time to UTC
Dim utcTime As DateTime = TimeZoneInfo.ConvertTimeToUtc(Me.Time, _
Me.TimeZone)
' Add time interval to time
utcTime = utcTime.Add(interval)
' Convert time back to time in time zone
Return New TimeZoneTime(Me.TimeZone, TimeZoneInfo.ConvertTime(utcTime, _
TimeZoneInfo.Utc, Me.TimeZone))
End Function
End Structure
Module TimeArithmetic
Public Const tzName As String = "Central Standard Time"
Public Sub Main()
Try
Dim cstTime1, cstTime2 As TimeZoneTime
Dim cst As TimeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById(tzName)
Dim time1 As Date = #03/09/2008 1:30AM#
Dim twoAndAHalfHours As New TimeSpan(2, 30, 0)
cstTime1 = New TimeZoneTime(cst, time1)
cstTime2 = cstTime1.AddTime(twoAndAHalfHours)
Console.WriteLine("{0} + {1} hours = {2}", cstTime1.Time, _
twoAndAHalfHours.ToString(), _
cstTime2.Time)
Catch
Console.WriteLine("Unable to find {0}.", tzName)
End Try
End Sub
End Module
Оба DateTime значения DateTimeOffset отсоединяются от любого часового пояса, к которому они могут принадлежать. Для того чтобы арифметические действия с датами и временем выполнялись таким образом, что правила коррекции часовых поясов учитывались бы автоматически, сведения о часовом поясе, к которому относятся значения даты и времени, должны быть известны и непосредственно доступны. Это означает, что значения даты и времени должны быть тесно связаны с часовым поясом. Для того, чтобы этого добиться, существует целый ряд способов, некоторые из них перечислены ниже.
Предположим, что все значения времени, используемые в приложении, принадлежат к определенному часовому поясу. Хотя этот подход вполне применим во многих случаях, его гибкость и, возможно, переносимость имеют ограничения.
Следует определить тип, в котором значение даты и времени тесно связано с часовым поясом, включив эти данные в состав типа в качестве полей. Этот подход используется в примере кода — в нем определяется структура для хранения даты, времени и часового пояса в двух полях структуры.
В примере показано, как выполнять арифметические операции со DateTime значениями, чтобы правила корректировки часового пояса применялись к результату. DateTimeOffset Однако значения можно использовать так же легко. В следующем примере показано, как код в исходном примере можно адаптировать для использования DateTimeOffset вместо значений DateTime .
using System;
public struct TimeZoneTime
{
public TimeZoneInfo TimeZone;
public DateTimeOffset Time;
public TimeZoneTime(TimeZoneInfo tz, DateTimeOffset time)
{
if (tz == null)
throw new ArgumentNullException("The time zone cannot be a null reference.");
this.TimeZone = tz;
this.Time = time;
}
public TimeZoneTime AddTime(TimeSpan interval)
{
// Convert time to UTC
DateTimeOffset utcTime = TimeZoneInfo.ConvertTime(this.Time, TimeZoneInfo.Utc);
// Add time interval to time
utcTime = utcTime.Add(interval);
// Convert time back to time in time zone
return new TimeZoneTime(this.TimeZone, TimeZoneInfo.ConvertTime(utcTime, this.TimeZone));
}
}
public class TimeArithmetic
{
public const string tzName = "Central Standard Time";
public static void Main()
{
try
{
TimeZoneTime cstTime1, cstTime2;
TimeZoneInfo cst = TimeZoneInfo.FindSystemTimeZoneById(tzName);
DateTime time1 = new DateTime(2008, 3, 9, 1, 30, 0);
TimeSpan twoAndAHalfHours = new TimeSpan(2, 30, 0);
cstTime1 = new TimeZoneTime(cst,
new DateTimeOffset(time1, cst.GetUtcOffset(time1)));
cstTime2 = cstTime1.AddTime(twoAndAHalfHours);
Console.WriteLine("{0} + {1} hours = {2}", cstTime1.Time,
twoAndAHalfHours.ToString(),
cstTime2.Time);
}
catch
{
Console.WriteLine("Unable to find {0}.", tzName);
}
}
}
Public Structure TimeZoneTime
Public TimeZone As TimeZoneInfo
Public Time As DateTimeOffset
Public Sub New(tz As TimeZoneInfo, time As DateTimeOffset)
If tz Is Nothing Then _
Throw New ArgumentNullException("The time zone cannot be a null reference.")
Me.TimeZone = tz
Me.Time = time
End Sub
Public Function AddTime(interval As TimeSpan) As TimeZoneTime
' Convert time to UTC
Dim utcTime As DateTimeOffset = TimeZoneInfo.ConvertTime(Me.Time, TimeZoneInfo.Utc)
' Add time interval to time
utcTime = utcTime.Add(interval)
' Convert time back to time in time zone
Return New TimeZoneTime(Me.TimeZone, TimeZoneInfo.ConvertTime(utcTime, Me.TimeZone))
End Function
End Structure
Module TimeArithmetic
Public Const tzName As String = "Central Standard Time"
Public Sub Main()
Try
Dim cstTime1, cstTime2 As TimeZoneTime
Dim cst As TimeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById(tzName)
Dim time1 As Date = #03/09/2008 1:30AM#
Dim twoAndAHalfHours As New TimeSpan(2, 30, 0)
cstTime1 = New TimeZoneTime(cst, _
New DateTimeOffset(time1, cst.GetUtcOffset(time1)))
cstTime2 = cstTime1.AddTime(twoAndAHalfHours)
Console.WriteLine("{0} + {1} hours = {2}", cstTime1.Time, _
twoAndAHalfHours.ToString(), _
cstTime2.Time)
Catch
Console.WriteLine("Unable to find {0}.", tzName)
End Try
End Sub
End Module
Обратите внимание, что если это дополнение просто выполняется для DateTimeOffset значения, не преобразовывая его в UTC, результат отражает правильный момент времени, но его смещение не отражает значение указанного часового пояса в течение этого времени.