安全地将UTC日期时间转换为当地时间(基于TZ)进行计算?

时间:2010-04-15 17:15:07

标签: c# datetime timezone utc datetimeoffset

关注我的last question @Jon Skeet给了我很多帮助(再次感谢!)

我现在想知道当他们被转换回本地日期/时间时,如何安全使用以UTC格式存储的日期/时间。

正如乔恩在我上一期提出的问题所指出的那样,使用DateTimeOffset及时表示即时并且没有办法预测一分钟后当地时间会说什么。我需要能够根据这些日期/时间进行计算。

那么,我如何保证从数据库中提取日期,将它们转换为本地日期/时间并对它们进行具体计算,它们将是准确的?

示例

我的应用程序记录通过电子邮件发送的信息。收到电子邮件的日期/时间记录为提交时间。电子邮件是从交换中提取的。

我需要知道的是:

1)如果这些电子邮件来自不同的国家/地区,我是否只将电子邮件的Recieved日期/时间转换为UTC格式并存储?例如Email.Received.ToUniversalTime()

2 个答案:

答案 0 :(得分:6)

不,你不能这么认为。 UTC时间是完全线性的,您可以安全地对它们进行计算。一旦将其转换为当地时间,它就不再是完全线性的。

当夏令时发生变化时,当地时间会有重叠或间隙。如果您进行的计算跨越夏令时变化,结果将会减少一个小时(如果这是多少时间变化,这是最常见的)。

如果在将DateTime / DateTimeOffset值转换为本地时间之前进行计算,结果将始终正确。但请注意,将值转换为本地DateTime值可能会使其不明确,如果值在夏令时变化时恰好落在重叠内,则无法判断它是第一次还是第二次确切时间发生在当天。

答案 1 :(得分:2)

正确处理日期/时间的最安全方法是将所有内容存储为UTC并以当地时间显示。所有日期/时间数学都应该按照Guffa建议的UTC进行。以UTC格式存储,并在显示时即时转换为当地时间。

如何制作时区感知日期/时间

Microsoft有一篇关于如何将DateTime和TimeZoneInfo变量封装到结构here中的文章。

这是Microsoft的示例结构,添加了1个属性以轻松获取本地时间。这需要更多的工作才能完全有用,但这是一个良好的开端。

public struct TimeZoneTime
{
   public TimeZoneInfo TimeZone;
   public DateTimeOffset Time;

   public TimeZoneTime(DateTimeOffset time)
   {
      this.TimeZone = TimeZone.Local;
      this.Time = 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 DateTime LocalDate 
    {
        get { return Time.ToOffset(TimeZone); }
    }
}

您的方案

  1. 是的,使用邮件对象的ReceivedTime或SentOn,并将其转换为UTC进行存储&计算。这比上面的样本复杂得多。

    Message msg = new Message();
    DateTime received = msg.ReceivedTime.ToUniversalTime();
    received.AddDays(7);
    Console.WriteLine(received.ToLocalTime());