【问题标题】:Safely convert UTC datetimes to local time (based on TZ) for calculations?安全地将 UTC 日期时间转换为本地时间(基于 TZ)进行计算?
【发布时间】:2011-02-08 12:01:55
【问题描述】:

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

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

正如 Jon 在我上一个问题中指出的那样,使用 DateTimeOffset 表示时间即时,无法预测一分钟后的当地时间。我需要能够根据这些日期/时间进行计算。

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

场景

我的申请记录了通过电子邮件发送的信息。收到电子邮件的日期/时间记录为提交时间。电子邮件是从 Exchange 中提取的。

我需要知道的是:

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

【问题讨论】:

  • 在将结果转换为本地时间之前,您不能在 UTC 日期/时间上执行计算吗?
  • 他们是否在本地有关系吗?如果 DateTimeOffset 只给我一个瞬间,那么计算是否准确?
  • 你真的错过了 UTC 的重点。在世界任何地方都是一样的。 不要转换
  • @Hans:我的意思是——假设我在英国有一个日期/时间,这个日期/时间在不同的时区是不一样的。所以说它是针对澳大利亚的,我需要将 UTC 时间转换为当地时间以便对其进行计算(因为英国时间的日期为 15/04/2010 18:00 会导致类似于 16/04/2010 04:00 澳大利亚时间)

标签: c# datetime timezone utc datetimeoffset


【解决方案1】:

不,你不能这样假设。 UTC 时间是完全线性的,您可以安全地对其进行计算。将其转换为当地时间后,它就不再是完全线性的了。

当夏令时发生变化时,本地时间会出现重叠或差距。如果您进行跨越夏令时更改的计算,则结果将相差一小时(如果这是最常见的时间更改量)。

如果您在将 DateTime/DateTimeOffset 值转换为本地时间之前进行计算,则结果将始终正确。但是请注意,将值转换为本地 DateTime 值可能会使其模棱两可,如果该值恰好落在夏令时更改时的重叠范围内,则无法判断该确切时间是第一次还是第二次出现。

【讨论】:

  • @Guffa:那么在存储日期/时间(例如澳大利亚)时,在保存之前将该 UTC 时间转换为等效的特定时区是否安全?这仍然代表UTC时间吗? (我的意思不是使用ToLocalTime() 我的意思是ConvertTimeBySystemTimeZoneId
  • @Guffa:我实际上并没有假设任何事情:)
  • @James:不,当您将值转换为时区时,它不再是 UTC 时间。但是,如果您使用 DateTimeOffset,它的偏移量将相应更改,因此您可以将其转换回原始 UTC 时间。 DateTimeOffset 值包含与 UTC 时间的偏移量,但不包含时区,因此虽然将其转换回 UTC 是安全的,但 DateTimeOffset 值的计算并未考虑夏令时。如果你转换为UTC,进行计算并转换回来,计算将是正确的。
  • @James:邮件中收到的日期是有时间偏移的,所以对应的是一个 DateTimeOffset 值。如果您可以成功地将其解析为 DateTimeOffset 值,则可以安全地将其转换为 UTC,并将 DateTime 值保存为明确的时间点。从中您可以将其转换为任何时区,以便您可以根据需要在任何本地时间显示它。
  • @Guffa 谢谢我想我终于开始明白这一切了!还有一件事。有时我需要解析用户自己发送的日期(例如在电子邮件正文中)。我必须将此日期视为当地时间。我将如何将其存储为 UTC?我是否需要获取时间偏移量(基于时区),然后将其转换为 DateTimeOffset,然后是 UTC 日期时间?
【解决方案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());
    

【讨论】:

  • @chilltemp:我应该将其存储为服务器时区 UTC 还是本地时区 UTC?
  • @James:我不明白你在服务器和本地时区 UTC 之间的意思。 UTC 就是 UTC,没有变化。
  • @James - 如果您不知道是使用服务器时间还是客户端时间,请始终使用服务器上的时间。 (在任何情况下,它都应该是 UTC,正如@chilltemp 所说。)使用服务器上的时间可以使所有用户的数据保持一致,并且对于安全性更好,因为您通常不控制客户端的时间设置。
  • @James:@Jeffery 是正确的。服务器时间最适合恒定。脱机客户端除外。如果客户端没有到服务器的活动通道,您将需要使用客户端的时间(如 UTC),并将在稍后的时间传递数据(例如批量通信)。
猜你喜欢
  • 2012-10-07
  • 1970-01-01
  • 2016-07-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-08-27
相关资源
最近更新 更多