DateTime.ToLocalTime 在夏令时无法正常工作

Posted

技术标签:

【中文标题】DateTime.ToLocalTime 在夏令时无法正常工作【英文标题】:DateTime.ToLocalTime not working properly with daylight savings 【发布时间】:2018-04-20 00:23:09 【问题描述】:

我有一个带有Kind = DateTimeKind.Utc 和时间跨度的DateTime 实例。

var dt = DateTime.UtcNow;
var ts = TimeSpan.FromDays(1);

由于夏令时,当我本地化 dt 然后添加 ts 时,我得到的结果与添加 ts 然后本地化时不同。

var localizedFirst = dt.ToLocalTime() + ts; //Does account for daylight savings
var addedFirst = (dt + ts).ToLocalTime(); //Does not account for daylight savings

这看起来很奇怪。不应该从本地化添加偏移量和从时间跨度添加偏移量是可交换和关联的吗?

我发现了一个类似的问题:Why doesn't DateTime.ToLocalTime() take into account daylight savings? 这个问题更多地涉及将DateTime 转换为String 和从String 转换。我只使用DateTimeTimeSpan 算术。

该问题的最佳答案建议使用DateTimeKind.Unspecified,以便运行时假定未指定的日期是UTC,然后在本地化时正确转换它。我很惊讶这真的有效。如果我像这样创建一个新的DateTime

var dt2 = new DateTime(dt.Ticks, DateTimeKind.Unspecified);

然后两个操作顺序都返回正确的结果,夏令时。

(dt2 + ts).ToLocalTime() 
dt2.ToLocalTime() + ts

这一切在我看来都是荒谬的。为什么我需要将Utc 日期转换为Unspecified 只是为了将其正确转换为Local?这似乎应该被视为一个错误。

其他细节:

框架:.NET 4.6.1 我的本地时区:东部标准时间(美国) dt 使用的实际值:11/5/2017 2:36:13pm UTC ts 使用的实际值:TimeSpan.FromDays(699) dt 的本地等效项:11/5/2017 9:36:13am (dt + ts).ToLocalTime() 的值:10/5/2019 10:36:13am dt.ToLocalTime() + ts 的值:10/5/2019 9:36:13am

【问题讨论】:

您使用的是什么版本的 .NET? 2.0/3.5 运行时处理日期的方式与 4.x 运行时不同。几年前,我遇到了很多差异。从那时起它们可能已经得到解决,但无论哪种方式,框架版本都会有所帮助。 也许这篇文章会有所帮助msdn.microsoft.com/en-us/library/ms973825.aspx 您能否提供具体的时间和时区(什么时区,dt 时间,夏令时在该地区生效的时间)?因为它对这类问题很重要。但总的来说 - 日期时间的算术运算(如添加天数 - 您的情况)不会考虑夏令时,而转换操作(如 ToLocalTime - 也是您的情况)会。 又到了一年的时间。发生的事情是完全正常的,(dt + ts) 仍然是 utc,因此可以针对 dst 进行适当调整。但当地时间 + ts 保持当地时间。并且无法调整,因为当地时间不明确。 这里还有一个重要的问题:您是否恰好在亚利桑那州或夏威夷(不针对 DST 进行调整的州)? 【参考方案1】:

此语句实际上要求提前一天,但所有其他属性(一天中的小时、分钟)保持不变:

var localizedFirst = dt.ToLocalTime() + ts;

虽然此语句询问正好 24 小时(经过的时间)过去后的当地时间:

var addedFirst = (dt + ts).ToLocalTime();

这是一个很好的论据,可以将所有内容保持 UTC 直到最后一分钟,然后转换为本地时间进行输出。

编辑:或者相反,如果您不希望在添加或减去天数时更改本地时间和分钟,请在添加 TimeSpan 之前转换为本地时间。 然而,正如马特约翰逊正确指出的那样,这样你最终可能会得到一个无效的本地时间(时钟在那个时间向前走)或模棱两可(时钟往回走,所以那个时间发生了两次)。有关如何确定这一点,请参阅下面的评论。

【讨论】:

是的,这是真的,除了localizedFirst 的结果可能无效或模棱两可。 TimeZoneInfo 中的 IsInvalidTimeIsAmbiguousTime 方法会告诉您它们是否存在,然后您可以根据需要进行相应调整。最终,这种“根据时区添加日历日”可能应该内置到框架中,但目前还没有。【参考方案2】:

几点:

TimeSpan 表示经过的持续时间。它的“天”是标准天,正好是 24 小时。

在相关日期,在当地时区,由于 DST 后备过渡,有 25 小时。

DateTime 对象的添加(通过+ 运算符或Add... 函数)总是在不考虑时区的情况下完成。换句话说,无论原来的.Kind 属性是什么,输出都将具有相同的.Kind 属性,但在加法/减法过程中根本不考虑种类。

因此,在转换为本地时间之后添加 不考虑 25 小时一天。这也是有问题的,因为它可能会落在一个不存在或存在两次的本地时间值上。

因此,当您在代码 cmets 中说“是否(或不)考虑夏令时”时,从技术上讲,您将其颠倒过来。由于 UTC 没有转换,localizedFirst 变量是错误地假设当地一天是 24 小时的结果,而addedFirst 变量是从当地时区正确应用 DST 规则的结果,即在时间轴上的原始点之后 24 小时过去了。

另外,设置DateTimeKind.Unspecified 不会改变这种情况下的效果,因为DateTime.ToLocalTime() 方法会将DateTimeKind.Unspecified 视为DateTimeKind.Utc。 See the table in the remarks of the documentation here. 确实,我试图复制您的结果,并且无法仅通过更改类型来使 dt2 的值有任何不同。如果可以,请具体说明这一点。

值得指出的是,消除这种混淆正是Noda Time 库存在的原因。在 Noda Time 中,这些由两个非常不同的操作来表示:

LocalDateTime + Period = LocalDateTime Instant + Duration = Instant

【讨论】:

如果这是一个新建项目,我肯定会使用 NodaTime,但 10 年后很难改变。 @JamesFaix 这样您就可以提供可重复的示例,说明未指定日期类型的帮助或您身边的一些错误? @Evk 我使用了11/5/2017 2:36:13pm UTC 的日期时间并添加了TimeSpan.FromDays(699)。如果我通过复制它的 Ticks 值创建了一个未指定的 DateTime,那么这些操作是可交换的,但如果我坚持使用 UTC 原件,它们就不是了。

以上是关于DateTime.ToLocalTime 在夏令时无法正常工作的主要内容,如果未能解决你的问题,请参考以下文章

如何在 SQL Server 中创建夏令时开始和结束函数

确定夏令时 (DST) 在 Java 中是不是在指定日期处于活动状态

中国有夏令时和冬令时吗?

Debian 在 R 中没有考虑夏令时

在 Linux 下使用 gcc 在 C 中的夏令时和 mktime

使用python确定时间戳是不是在夏令时