如何:在日期和时间算法中使用时区

更新:2007 年 11 月

通常,在使用 DateTimeDateTimeOffset 值执行日期和时间的算术运算时,所得的结果并不能反映任何时区调整规则。即使可以清楚地识别日期和时间值的时区(例如,当 Kind 属性设置为 Local 时),情况也是如此。本主题介绍如何对属于某一特定时区的日期和时间值执行算术运算。这种算术运算的结果将反映时区的调整规则。

对日期和时间算法应用调整规则

  1. 通过实现某种方法将日期和时间值与其所属的时区紧密耦合起来。例如,声明一个同时包含日期和时间值及其时区的结构。下面的示例使用此方法将 DateTime 值与其时区关联起来。

    ' Define a structure for DateTime values for internal use only
    Friend Structure TimeWithTimeZone
       Dim TimeZone As TimeZoneInfo
       Dim Time As Date
    End Structure
    
    // Define a structure for DateTime values for internal use only
    internal struct TimeWithTimeZone
    {
       TimeZoneInfo TimeZone;
       DateTime Time;
    }
    
  2. 通过调用 ConvertTimeToUtc 方法或 ConvertTime 方法将时间转换为协调世界时 (UTC)。

  3. 对 UTC 时间执行算术运算。

  4. 通过调用 TimeZoneInfo.ConvertTime(DateTime, TimeZoneInfo) 方法将时间从 UTC 转换为与原始时间关联的时区。

示例

下面的示例向中部标准时间 2008 年 3 月 9 日凌晨 1:30 添加了两小时三十分钟。三十分钟后(即 2008 年 3 月 9 日凌晨 2:00),该时区会转换到夏时制。由于该示例遵从了上一节中列出的四个步骤,因此它将结果时间正确地报告为 2008 年 3 月 9 日凌晨 5:00。

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
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);
      }
   }
}

DateTimeDateTimeOffset 值都会与它们可能所属的任何时区解除关联。若要在执行日期和时间算术运算时自动应用时区调整规则,任何日期和时间值所属的时区都必须可以立即识别。这意味着,任何日期和时间均必须和与之关联的时区紧密耦合在一起。有多种方法可以实现此目的,其中包括:

  • 假定应用程序中使用的所有时间都属于某个特定的时区。虽然此方法在某些情况下可行,但它的灵活性有限,可移植性也可能不大。

  • 定义一种类型,并将日期和时间以及与之关联的时区作为两个字段包含在该类型中,从而将二者紧密地耦合在一起。代码示例中使用的就是此方法,该示例定义了一个结构,并将日期和时间以及时区作为两个成员字段存储在该结构中。

该示例演示如何对 DateTime 值执行算术运算,以便向结果应用时区调整规则。但是,DateTimeOffset 值有更简单的使用方法。下面的示例演示如何改写原始示例中的代码,以使用 DateTimeOffset 代替 DateTime 值。

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
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);
      }
   }
}

请注意,如果在未将 DateTimeOffset 值转换为 UTC 的情况下便直接对其执行此加法运算,其结果将反映正确的时间点,但其偏移量将不会反映该时间的指定时区的偏移量。

编译代码

此示例要求:

  • 在项目中添加一个对 System.Core.dll 的引用。

  • 使用 using 语句导入 System 命名空间(在 C# 代码中需要)。

请参见

概念

使用日期和时间执行算术运算

其他资源

时间和时区