Поделиться через


Практическое руководство. Использование часовых поясов в арифметических операциях с датами и временем

Обычно при выполнении арифметики даты и времени с использованием DateTime или DateTimeOffset значениями результат не отражает правила корректировки часового пояса. Это верно, даже если часовой пояс значения даты и времени четко идентифицируется (например, если Kind свойству присвоено значение Local). В этом разделе показано, как выполнять арифметические операции с значениями даты и времени, принадлежащими определенному часовому поясу. Результаты арифметических операций при этом будут учитывать правила коррекции часовых поясов.

Применение правил коррекции в вычислениях с датами и временем

  1. Необходимо каким-либо способом тесно связать значения даты и времени с соответствующим часовым поясом. К примеру, можно объявить структуру, которая будет содержать значение даты и времени вместе с данными о часовом поясе. В следующем примере используется этот подход для связывания 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
    
  2. Преобразуйте время в универсальное время (UTC), вызвав ConvertTimeToUtc метод или ConvertTime метод.

  3. Далее следует выполнить необходимые арифметические действия над временем UTC.

  4. Преобразуйте время из 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)
    {
        ArgumentNullException.ThrowIfNull(tz);

        TimeZone = tz;
        Time = time;
    }

    public TimeZoneTime AddTime(TimeSpan interval)
    {
        // Convert time to UTC
        DateTime utcTime = TimeZoneInfo.ConvertTimeToUtc(Time, TimeZone);
        // Add time interval to time
        utcTime = utcTime.Add(interval);
        // Convert time back to time in time zone
        return new TimeZoneTime(TimeZone, TimeZoneInfo.ConvertTime(utcTime,
                                TimeZoneInfo.Utc, 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(2008, 3, 9, 1, 30, 0);
            TimeSpan twoAndAHalfHours = new(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 {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 TimeZoneTime2
{
    public TimeZoneInfo TimeZone;
    public DateTimeOffset Time;

    public TimeZoneTime2(TimeZoneInfo tz, DateTimeOffset time)
    {
        ArgumentNullException.ThrowIfNull(tz);

        TimeZone = tz;
        Time = time;
    }

    public TimeZoneTime2 AddTime(TimeSpan interval)
    {
        // Convert time to UTC
        DateTimeOffset utcTime = TimeZoneInfo.ConvertTime(Time, TimeZoneInfo.Utc);
        // Add time interval to time
        utcTime = utcTime.Add(interval);
        // Convert time back to time in time zone
        return new TimeZoneTime2(TimeZone, TimeZoneInfo.ConvertTime(utcTime, TimeZone));
    }
}

public class TimeArithmetic2
{
    public const string tzName = "Central Standard Time";

    public static void Main()
    {
        try
        {
            TimeZoneTime2 cstTime1, cstTime2;

            TimeZoneInfo cst = TimeZoneInfo.FindSystemTimeZoneById(tzName);
            DateTime time1 = new(2008, 3, 9, 1, 30, 0);
            TimeSpan twoAndAHalfHours = new(2, 30, 0);

            cstTime1 = new TimeZoneTime2(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 {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, результат отражает правильный момент времени, но его смещение не отражает значение указанного часового пояса в течение этого времени.

См. также