在不同时区之间转换时间

对于处理日期和时间的任何应用程序而言,正确处理不同时区之间的差异愈发重要。 应用程序不能再假定所有时间都可以表示为本地时间(DateTime 结构中的时间)。 例如,显示美国东部当前时间的网页对于东亚地区的客户来说便缺乏可信度。 本主题将说明如何在不同时区之间转换时间,以及如何转换可提供有限时区识别能力的 DateTimeOffset 值。

转换为协调世界时

协调世界时 (UTC) 是一个高精度的原子时间标准。 世界上的所有时区都可以表示为 UTC 加上或减去一个偏移量。 因此,UTC 提供了一种与时区无关(或非特定于时区)的时间。 如果日期和时间在计算机之间的可迁移性非常重要,则建议使用 UTC 时间。 (有关详细信息以及使用日期和时间的其他最佳方案,请参见 Coding Best Practices Using DateTime in the .NET Framework(在 .NET Framework 中使用 DateTime 的最佳编程方案)。)通过将各个时区转换为 UTC,可以简化时间的比较。

注意注意

还可以序列化 DateTimeOffset 结构,以便明确地表示单个时间点。由于 DateTimeOffset 对象存储了日期和时间值及其 UTC 偏移量,因此它们表示的特定时间点始终与 UTC 有关。

若要将时间转换为 UTC,最简单的方法是调用 static(在 Visual Basic 中为 Shared)TimeZoneInfo.ConvertTimeToUtc(DateTime) 方法。 该方法所执行的具体转换取决于 dateTime 参数的 Kind 属性值,如下表所示。

DateTime.Kind 属性

转换

DateTimeKind.Local

将本地时间转换为 UTC。

DateTimeKind.Unspecified

将 dateTime 参数假定为本地时间并将本地时间转换为 UTC。

DateTimeKind.Utc

返回未更改的 dateTime 参数。

下面的代码将当前本地时间转换为 UTC 并将结果显示在控制台上。

Dim dateNow As Date = Date.Now      
Console.WriteLine("The date and time are {0} UTC.", _
                  TimeZoneInfo.ConvertTimeToUtc(dateNow))
DateTime dateNow = DateTime.Now;
Console.WriteLine("The date and time are {0} UTC.", 
                   TimeZoneInfo.ConvertTimeToUtc(dateNow));
注意注意

TimeZoneInfo.ConvertTimeToUtc(DateTime) 方法生成的结果不必与 TimeZone.ToUniversalTimeDateTime.ToUniversalTime 方法相同。如果主机系统的本地时区包含多个调整规则,TimeZoneInfo.ConvertTimeToUtc(DateTime) 将向特定的日期和时间应用相应的规则。其他两个方法始终应用最新的调整规则。

如果日期和时间值不表示本地时间或 UTC,ToUniversalTime 方法将可能返回错误的结果。 但是,可以使用 TimeZoneInfo.ConvertTimeToUtc 方法转换指定时区的日期和时间 (有关检索表示目标时区的 TimeZoneInfo 对象的详细信息,请参见查找在本地系统上定义的时区)。下面的代码使用 TimeZoneInfo.ConvertTimeToUtc 方法将东部标准时间转换为 UTC。

Dim easternTime As New Date(2007, 01, 02, 12, 16, 00)
Dim easternZoneId As String = "Eastern Standard Time"
Try
   Dim easternZone As TimeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById(easternZoneId)
   Console.WriteLine("The date and time are {0} UTC.", _ 
                     TimeZoneInfo.ConvertTimeToUtc(easternTime, easternZone))
Catch e As TimeZoneNotFoundException
   Console.WriteLine("Unable to find the {0} zone in the registry.", _
                     easternZoneId)
Catch e As InvalidTimeZoneException
   Console.WriteLine("Registry data on the {0} zone has been corrupted.", _ 
                     easternZoneId)
End Try                           
DateTime easternTime = new DateTime(2007, 01, 02, 12, 16, 00);
string easternZoneId = "Eastern Standard Time";
try
{
   TimeZoneInfo easternZone = TimeZoneInfo.FindSystemTimeZoneById(easternZoneId);
   Console.WriteLine("The date and time are {0} UTC.", 
                     TimeZoneInfo.ConvertTimeToUtc(easternTime, easternZone));
}
catch (TimeZoneNotFoundException)
{
   Console.WriteLine("Unable to find the {0} zone in the registry.", 
                     easternZoneId);
}                           
catch (InvalidTimeZoneException)
{
   Console.WriteLine("Registry data on the {0} zone has been corrupted.", 
                     easternZoneId);
}

请注意,如果 DateTime 对象的 Kind 属性与时区不匹配,此方法将引发 ArgumentException。 如果 Kind 属性为 DateTimeKind.LocalTimeZoneInfo 对象并不表示本地时区,或者如果 Kind 属性为 DateTimeKind.UtcTimeZoneInfo 对象并不等于 DateTimeKind.Utc,则将出现不匹配的情况。

所有这些方法都将 DateTime 值作为参数并返回一个 DateTime 值。 对于 DateTimeOffset 值,DateTimeOffset 结构都有一个 ToUniversalTime 实例方法,该方法可将当前实例的日期和时间转换为 UTC。 下面的示例通过调用 ToUniversalTime 方法将本地时间以及几个其他时间转换为协调世界时 (UTC)。

Dim localTime, otherTime, universalTime As DateTimeOffset

' Define local time in local time zone
localTime = New DateTimeOffset(#6/15/2007 12:00:00PM#)
Console.WriteLine("Local time: {0}", localTime)
Console.WriteLine()

' Convert local time to offset 0 and assign to otherTime
otherTime = localTime.ToOffset(TimeSpan.Zero)
Console.WriteLine("Other time: {0}", otherTime)
Console.WriteLine("{0} = {1}: {2}", _
                  localTime, otherTime, _
                  localTime.Equals(otherTime))
Console.WriteLine("{0} exactly equals {1}: {2}", _ 
                  localTime, otherTime, _
                  localTime.EqualsExact(otherTime))
Console.WriteLine()

' Convert other time to UTC
universalTime = localTime.ToUniversalTime() 
Console.WriteLine("Universal time: {0}", universalTime)
Console.WriteLine("{0} = {1}: {2}", _
                  otherTime, universalTime, _ 
                  universalTime.Equals(otherTime))
Console.WriteLine("{0} exactly equals {1}: {2}", _ 
                  otherTime, universalTime, _
                  universalTime.EqualsExact(otherTime))
Console.WriteLine()
' The example produces the following output to the console:
'    Local time: 6/15/2007 12:00:00 PM -07:00
'    
'    Other time: 6/15/2007 7:00:00 PM +00:00
'    6/15/2007 12:00:00 PM -07:00 = 6/15/2007 7:00:00 PM +00:00: True
'    6/15/2007 12:00:00 PM -07:00 exactly equals 6/15/2007 7:00:00 PM +00:00: False
'    
'    Universal time: 6/15/2007 7:00:00 PM +00:00
'    6/15/2007 7:00:00 PM +00:00 = 6/15/2007 7:00:00 PM +00:00: True
'    6/15/2007 7:00:00 PM +00:00 exactly equals 6/15/2007 7:00:00 PM +00:00: True   
DateTimeOffset localTime, otherTime, universalTime;

// Define local time in local time zone
localTime = new DateTimeOffset(new DateTime(2007, 6, 15, 12, 0, 0));
Console.WriteLine("Local time: {0}", localTime);
Console.WriteLine();

// Convert local time to offset 0 and assign to otherTime
otherTime = localTime.ToOffset(TimeSpan.Zero);
Console.WriteLine("Other time: {0}", otherTime);
Console.WriteLine("{0} = {1}: {2}", 
                  localTime, otherTime, 
                  localTime.Equals(otherTime));
Console.WriteLine("{0} exactly equals {1}: {2}", 
                  localTime, otherTime, 
                  localTime.EqualsExact(otherTime));
Console.WriteLine();

// Convert other time to UTC
universalTime = localTime.ToUniversalTime(); 
Console.WriteLine("Universal time: {0}", universalTime);
Console.WriteLine("{0} = {1}: {2}", 
                  otherTime, universalTime, 
                  universalTime.Equals(otherTime));
Console.WriteLine("{0} exactly equals {1}: {2}", 
                  otherTime, universalTime, 
                  universalTime.EqualsExact(otherTime));
Console.WriteLine();
// The example produces the following output to the console:
//    Local time: 6/15/2007 12:00:00 PM -07:00
//    
//    Other time: 6/15/2007 7:00:00 PM +00:00
//    6/15/2007 12:00:00 PM -07:00 = 6/15/2007 7:00:00 PM +00:00: True
//    6/15/2007 12:00:00 PM -07:00 exactly equals 6/15/2007 7:00:00 PM +00:00: False
//    
//    Universal time: 6/15/2007 7:00:00 PM +00:00
//    6/15/2007 7:00:00 PM +00:00 = 6/15/2007 7:00:00 PM +00:00: True
//    6/15/2007 7:00:00 PM +00:00 exactly equals 6/15/2007 7:00:00 PM +00:00: True   

将 UTC 转换为指定的时区

若要将 UTC 转换为本地时间,请参见下面的“将 UTC 转换为本地时间”一节。 若要将 UTC 转换为任何指定时区中的时间,请调用 ConvertTimeFromUtc 方法。 该方法有两个参数:

下面的代码将 UTC 转换为中部标准时间。

Dim timeUtc As Date = Date.UtcNow
Try
   Dim cstZone As TimeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById("Central Standard Time")
   Dim cstTime As Date = TimeZoneInfo.ConvertTimeFromUtc(timeUtc, cstZone)
   Console.WriteLine("The date and time are {0} {1}.", _
                     cstTime, _
                     IIf(cstZone.IsDaylightSavingTime(cstTime), _
                         cstZone.DaylightName, cstZone.StandardName))
Catch e As TimeZoneNotFoundException
   Console.WriteLine("The registry does not define the Central Standard Time zone.")
Catch e As InvalidTimeZoneException
   Console.WriteLine("Registry data on the Central Standard Time zone has been corrupted.")
End Try
DateTime timeUtc = DateTime.UtcNow;
try
{
   TimeZoneInfo cstZone = TimeZoneInfo.FindSystemTimeZoneById("Central Standard Time");
   DateTime cstTime = TimeZoneInfo.ConvertTimeFromUtc(timeUtc, cstZone);
   Console.WriteLine("The date and time are {0} {1}.", 
                     cstTime, 
                     cstZone.IsDaylightSavingTime(cstTime) ?
                             cstZone.DaylightName : cstZone.StandardName);
}
catch (TimeZoneNotFoundException)
{
   Console.WriteLine("The registry does not define the Central Standard Time zone.");
}                           
catch (InvalidTimeZoneException)
{
   Console.WriteLine("Registry data on the Central STandard Time zone has been corrupted.");
}

将 UTC 转换为本地时间

若要将 UTC 转换为本地时间,请调用要转换其时间的 DateTime 对象的 ToLocalTime 方法。 该方法的具体行为取决于该对象的 Kind 属性值,如下表所示。

DateTime.Kind 属性

转换

DateTimeKind.Local

返回未更改的 DateTime 值。

DateTimeKind.Unspecified

假定 DateTime 值为 UTC 并将 UTC 转换为本地时间。

DateTimeKind.Utc

DateTime 值转换为本地时间。

注意TimeZone.ToLocalTime 方法的行为与 DateTime.ToLocalTime 方法完全相同。 该方法带有一个参数,即要转换的日期和时间值。

另外,您还可以使用 static(在 Visual Basic 中为 Shared)TimeZoneInfo.ConvertTime 方法将任何指定时区中的时间转换为本地时间。 下一节将对此技术加以讨论。

在任意两个时区之间转换

通过使用 TimeZoneInfo 类的以下两个 static(在 Visual Basic 中为 Shared)方法当中的任意一个,可以在任意两个时区之间进行转换:

  • ConvertTime

    此方法有三个参数:要转换的日期和时间值、一个表示该日期和时间值的时区的 TimeZoneInfo 对象以及一个表示要将该日期和时间值转换到的时区的 TimeZoneInfo 对象。

  • ConvertTimeBySystemTimeZoneId

    此方法也有三个参数:要转换的日期和时间值、该日期和时间值的时区的标识符以及要将该日期和时间值转换到的时区的标识符。

这两个方法都要求让要转换的日期和时间值的 Kind 属性与表示其时区的 TimeZoneInfo 对象或时区标识符相互对应。 否则会引发 ArgumentException。 例如,如果日期和时间值的 Kind 属性为 DateTimeKind.Local,则当作为参数传递给方法的 TimeZoneInfo 对象不等于 TimeZoneInfo.Local 时,便会引发异常。 另外,如果作为参数传递给方法的标识符不等于 TimeZoneInfo.Local.Id,则也会引发异常。

下面的示例使用 ConvertTime 方法将夏威夷标准时间转换为本地时间。

Dim hwTime As Date = #2/01/2007 8:00:00 AM#
Try
   Dim hwZone As TimeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById("Hawaiian Standard Time")
   Console.WriteLine("{0} {1} is {2} local time.", _
                     hwTime, _
                     IIf(hwZone.IsDaylightSavingTime(hwTime), hwZone.DaylightName, hwZone.StandardName), _
                     TimeZoneInfo.ConvertTime(hwTime, hwZone, TimeZoneInfo.Local))
Catch e As TimeZoneNotFoundException
   Console.WriteLine("The registry does not define the Hawaiian Standard Time zone.")
Catch e As InvalidTimeZoneException
   Console.WriteLine("Registry data on the Hawaiian Standard Time zone has been corrupted.")
End Try                     
DateTime hwTime = new DateTime(2007, 02, 01, 08, 00, 00);
try
{
   TimeZoneInfo hwZone = TimeZoneInfo.FindSystemTimeZoneById("Hawaiian Standard Time");
   Console.WriteLine("{0} {1} is {2} local time.", 
           hwTime, 
           hwZone.IsDaylightSavingTime(hwTime) ? hwZone.DaylightName : hwZone.StandardName, 
           TimeZoneInfo.ConvertTime(hwTime, hwZone, TimeZoneInfo.Local));
}
catch (TimeZoneNotFoundException)
{
   Console.WriteLine("The registry does not define the Hawaiian Standard Time zone.");
}                           
catch (InvalidTimeZoneException)
{
   Console.WriteLine("Registry data on the Hawaiian STandard Time zone has been corrupted.");
}

转换 DateTimeOffset 值

通过由 DateTimeOffset 对象表示的日期和时间值并不能完全确定时区,因为该对象在实例化时会解除与其时区的关联。 但在很多情况下,应用程序只需基于两个不同的 UTC 偏移量而不是特定时区中的时间即可完成日期和时间的转换。 若要执行此转换,可以调用当前实例的 ToOffset 方法。 该方法有一个参数,即该方法要返回的新日期和时间值的偏移量。

例如,如果已知用户请求网页的日期和时间,且该日期和时间已用 MM/dd/yyyy hh:mm:ss zzzz 格式序列化为一个字符串,则下面的 ReturnTimeOnServer 方法会将此日期和时间值转换为 Web 服务器上的日期和时间。

Public Function ReturnTimeOnServer(clientString As String) As DateTimeOffset
   Dim format As String = "M/d/yyyy H:m:s zzz"
   Dim serverOffset As TimeSpan = TimeZoneInfo.Local.GetUtcOffset(DateTimeOffset.Now)

   Try      
      Dim clientTime As DateTimeOffset = DateTimeOffset.ParseExact(clientString, format, CultureInfo.InvariantCulture)
      Dim serverTime As DateTimeOffset = clientTime.ToOffset(serverOffset)
      Return serverTime
   Catch e As FormatException
      Return DateTimeOffset.MinValue
   End Try    
End Function
public DateTimeOffset ReturnTimeOnServer(string clientString)
{
   string format = @"M/d/yyyy H:m:s zzz";
   TimeSpan serverOffset = TimeZoneInfo.Local.GetUtcOffset(DateTimeOffset.Now);

   try
   {      
      DateTimeOffset clientTime = DateTimeOffset.ParseExact(clientString, format, CultureInfo.InvariantCulture);
      DateTimeOffset serverTime = clientTime.ToOffset(serverOffset);
      return serverTime;
   }
   catch (FormatException)
   {
      return DateTimeOffset.MinValue;
   }
}

如果向该方法传递字符串“9/1/2007 5:32:07 -05:00”(表示比 UTC 早五个小时的时区中的日期和时间),则对于处在美国太平洋标准时区中的服务器, 它将返回 9/1/2007 3:32:07 AM -07:00。

TimeZoneInfo 类还包含 TimeZoneInfo.ConvertTime(DateTimeOffset, TimeZoneInfo) 方法的重载,该重载使用 DateTimeOffset 值执行时区转换。 该方法有两个参数:一个是 DateTimeOffset 值,另一个是对要将时间转换到的时区的引用。 该方法调用会返回 DateTimeOffset 值。 例如,可按以下方式重写上一示例中的 ReturnTimeOnServer 方法,以调用 ConvertTime(DateTimeOffset, TimeZoneInfo) 方法。

Public Function ReturnTimeOnServer(clientString As String) As DateTimeOffset
   Dim format As String = "M/d/yyyy H:m:s zzz"

   Try      
      Dim clientTime As DateTimeOffset = DateTimeOffset.ParseExact(clientString, format, CultureInfo.InvariantCulture)
      Dim serverTime As DateTimeOffset = TimeZoneInfo.ConvertTime(clientTime, TimeZoneInfo.Local)
      Return serverTime
   Catch e As FormatException
      Return DateTimeOffset.MinValue
   End Try    
End Function
public DateTimeOffset ReturnTimeOnServer(string clientString)
{
   string format = @"M/d/yyyy H:m:s zzz";

   try
   {      
      DateTimeOffset clientTime = DateTimeOffset.ParseExact(clientString, format, 
                                  CultureInfo.InvariantCulture);
      DateTimeOffset serverTime = TimeZoneInfo.ConvertTime(clientTime, 
                                  TimeZoneInfo.Local);
      return serverTime;
   }
   catch (FormatException)
   {
      return DateTimeOffset.MinValue;
   }
}

请参见

参考

TimeZoneInfo

概念

查找在本地系统上定义的时区

其他资源

日期、时间和时区