【问题标题】:DateTime - Strange daylight savings behaviourDateTime - 奇怪的夏令时行为
【发布时间】:2012-03-20 12:30:46
【问题描述】:

我的本​​地时区是 (UTC+10:00) 堪培拉、墨尔本、悉尼

2012 年 3 月 31 日星期六 15:59 UTC = 2012 年 4 月 1 日星期日 02:59 +11:00
2012 年 3 月 31 日星期六 16:00 UTC = 2012 年 4 月 1 日星期日 02:00 +10:00

夏令时在 4 月的第一个星期日凌晨 3 点结束,时钟拨回 1 小时。

给定以下代码 ....

DateTime dt1 = DateTime.Parse("31-Mar-2012 15:59", CultureInfo.CurrentCulture, DateTimeStyles.AssumeUniversal);

DateTime dt2 = DateTime.Parse("31-Mar-2012 15:59", CultureInfo.CurrentCulture, DateTimeStyles.AssumeUniversal).AddMinutes(1);
DateTime dt3 = DateTime.Parse("31-Mar-2012 16:00", CultureInfo.CurrentCulture, DateTimeStyles.AssumeUniversal);

Console.WriteLine("{0:yyyy-MMM-dd HH:mm:ss.ffff K}", dt1);
Console.WriteLine("{0:yyyy-MMM-dd HH:mm:ss.ffff K} ({1}) = {2:yyyy-MMM-dd HH:mm:ss.ffff K} ({3})", dt2, dt2.Kind, dt3, dt3.Kind);
Console.WriteLine("{0} : {1} : {2}", dt1.ToUniversalTime().Hour, dt2.ToUniversalTime().Hour, dt3.ToUniversalTime().Hour);

我得到以下输出

2012 年 4 月 1 日 02:59:00.0000 +11:00
2012 年 4 月 1 日 03:00:00.0000 +10:00(本地)= 2012 年 4 月 1 日 02:00:00.0000 +10:00(本地)
15:17:16

在原始日期时间上增加 1 分钟会使当地时间为凌晨 3 点,但也会将偏移量设置为 +10 小时。 将 1 分钟添加到 UTC 日期并正确解析将本地时间设置为凌晨 2 点,并带有 +10 UTC 偏移量。

重复

DateTime dt1 = new DateTime(2012, 03, 31, 15, 59, 0, DateTimeKind.Utc);

DateTime dt2 = new DateTime(2012, 03, 31, 15, 59, 0, DateTimeKind.Utc).AddMinutes(1);
DateTime dt3 = new DateTime(2012, 03, 31, 16, 0, 0, DateTimeKind.Utc);

DateTime dt1 = DateTime.Parse("31-Mar-2012 15:59", CultureInfo.CurrentCulture, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal);

DateTime dt2 = DateTime.Parse("31-Mar-2012 15:59", CultureInfo.CurrentCulture, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal).AddMinutes(1);
DateTime dt3 = DateTime.Parse("31-Mar-2012 16:00", CultureInfo.CurrentCulture, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal); 

给予

2012-Mar-31 15:59:00.0000 Z
2012 年 3 月 31 日 16:00:00.0000 Z (UTC) = 2012 年 3 月 31 日 16:00:00.0000 Z (UTC)
15:16:16

如预期的那样

再次重复

DateTime dt1 = new DateTime(2012, 03, 31, 15, 59, 0, DateTimeKind.Utc).ToLocalTime();

DateTime dt2 = new DateTime(2012, 03, 31, 15, 59, 0, DateTimeKind.Utc).ToLocalTime().AddMinutes(1);
DateTime dt3 = new DateTime(2012, 03, 31, 16, 0, 0, DateTimeKind.Utc).ToLocalTime();

给出原件

2012 年 4 月 1 日 02:59:00.0000 +11:00
2012 年 4 月 1 日 03:00:00.0000 +10:00(本地)= 2012 年 4 月 1 日 02:00:00.0000 +10:00(本地)
15:17:16

谁能解释一下?

如果我使用 TimeZoneInfo 从 UTC 转换为澳大利亚东部标准时间,我会得到正确的时间,但我会丢失 DateTime 实例中的偏移信息,因为 DateTime.Kind == DateTimeKind.Unspecified

== 要突出显示的其他场景

这只是简单的时间跨度添加,从一个明确的 UTC 日期开始,在夏令时结束前 1 分钟。

DateTime dt1 = new DateTime(2012, 03, 31, 15, 59, 0, DateTimeKind.Utc);  
DateTime dt2 = new DateTime(2012, 03, 31, 15, 59, 0, DateTimeKind.Utc).ToLocalTime();  

Console.WriteLine("Original in UTC     : {0:yyyy-MMM-dd HH:mm:ss.ffff K}", dt1);  
Console.WriteLine("Original in Local   : {0:yyyy-MMM-dd HH:mm:ss.ffff K}", dt1.ToLocalTime());  
Console.WriteLine("+ 1 Minute in Local : {0:yyyy-MMM-dd HH:mm:ss.ffff K}", dt1.AddMinutes(1).ToLocalTime());  
Console.WriteLine("+ 1 Minute in UTC   : {0:yyyy-MMM-dd HH:mm:ss.ffff K}", dt1.AddMinutes(1));  
Console.WriteLine("=====================================================");
Console.WriteLine("Original in UTC     : {0:yyyy-MMM-dd HH:mm:ss.ffff K}", dt2.ToUniversalTime());  
Console.WriteLine("Original in Local   : {0:yyyy-MMM-dd HH:mm:ss.ffff K}", dt2);  
Console.WriteLine("+ 1 Minute in Local : {0:yyyy-MMM-dd HH:mm:ss.ffff K}", dt2.AddMinutes(1));  
Console.WriteLine("+ 1 Minute in UTC   : {0:yyyy-MMM-dd HH:mm:ss.ffff K}", dt2.AddMinutes(1).ToUniversalTime());  

给予

UTC 原文:2012-Mar-31 15:59:00.0000 Z
本地原文:2012-Apr-01 02:59:00.0000 +11:00
+ 本地 1 分钟:2012 年 4 月 1 日 02:00:00.0000 +10:00
+ UTC 1 分钟:2012 年 3 月 31 日 16:00:00.0000 Z

================================================ ======

UTC 原文:2012-Mar-31 15:59:00.0000 Z
本地原文:2012-Apr-01 02:59:00.0000 +11:00
+ 本地 1 分钟:2012 年 4 月 1 日 03:00:00.0000 +10:00
+ UTC 1 分钟:2012 年 3 月 31 日 17:00:00.0000 Z

【问题讨论】:

  • DateTime 不会“保留”本地偏移量。我只是显示了当时有效的偏移量。由于 dt2 始终是本地时间,因此它对当前时间的看法是“真实的”。如果您想携带“应用时”偏移量msdn.microsoft.com/en-us/library/system.datetimeoffset.aspx,则应该使用 DateTimeOffset
  • ... 如果这是真的,那么我预计最后一个场景中 dt2 的第三个结果是 03:00:00 +11:00,但它知道 DST 已经结束。它正确切换到 +10:00,但没有起飞。 DateTimeOffset 显示时间为 03:00:00 +11:00,这对我的本地时区无效。
  • 不,它知道您说这是 2012 年 4 月 1 日的 3:00,所以当时的偏移量是 +10:00
  • 我从没说过是 03:00 +10,我在 2:59 +11 加了 1 分钟。它应该导致 02:00 +10 该日期的 DateTime.Kind 属性是 Local
  • @RobertSlaney:这就是问题所在 - 它正在进行局部算术。当你有一个本地的 DateTimeKind 时,它不会考虑任何 DST;您没有添加“经过”时间,您只是添加到本地时间。

标签: .net datetime dst


【解决方案1】:

我认为问题在于何时执行转换。

您正在解析假设通用时间,但随后隐式转换为“本地”类型 - 值为 2:59:59。当您要求“本地”值添加一分钟时,它只是在本地值上添加一分钟,而不考虑时区。然后,当您打印偏移量时,系统会尝试在当地时间凌晨 3 点计算出偏移量......这是 +10。

如此有效地你得到了:

  • 解析步骤 1:将字符串视为通用字符串 (15:59 UTC)
  • 解析步骤 2:将结果转换为本地(本地 2:59)
  • 补充:在当地时间,不应用时区值(当地时间 3:00)
  • 格式化第 1 步:请求偏移量,因此计算本地时间映射到的时间(17:00 UTC)
  • 格式化步骤 2:将偏移量计算为本地和通用 (+10) 之间的差异

是的,这有点痛苦 - DateTime is painful in general,这是我写 Noda Time 的主要原因,其中“区域中的日期/时间”与“本地日期/时间”有不同的类型(或“本地日期”或“本地时间”),很明显您在任何时候都在使用。

我不清楚您在这里实际想要实现什么 - 如果您可以更具体一些,我可以向您展示您在野田时间会做什么,尽管可能存在一些固有的歧义(从本地日期转换/ “分区”日期/时间的时间可以有 0、1 或 2 个结果)。

编辑:如果目标只是记住时区和瞬间,在野田时间你会想要ZonedDateTime,像这样:

using System;
using NodaTime;

class Program
{
    static void Main(string[] args)
    {
        var zone = DateTimeZone.ForId("Australia/Melbourne");
        ZonedDateTime start = Instant.FromUtc(2012, 3, 31, 15, 59, 0)
                                     .InZone(zone);
        ZonedDateTime end = start + Duration.FromMinutes(1);

        Console.WriteLine("{0} ({1})", start.LocalDateTime, start.Offset);
        Console.WriteLine("{0} ({1})", end.LocalDateTime, end.Offset);
    }
}

有关更多信息,请参阅calendar arithmetic 上的注释。

【讨论】:

  • 我在你的博客上看到了这篇文章,当时正在编写一些关于夏令时的单元测试,以验证 Noda Time DST 处理,但是当我从 DateTime 结构中看到这些结果时卡住了
  • 将此答案标记为正确,因为各种讨论都强调了非 UTC 日期时间的 DateTime 算法在 .NET 中根本被破坏。解决这个问题的唯一方法是包装或替换 DateTime
【解决方案2】:

我处理这个问题的方法是把 DateTime 有点像浮点数——当你在操作它们和向用户展示它们时,它们需要特殊处理。我使用我编写的一个小库来包装它们:

https://github.com/b9chris/TimeZoneInfoLib.Net

并始终将它们视为 UTC + TimeZoneInfo。这样你就可以做你通常做的所有典型的数学运算,只操作 UTC 到 UTC,并且只在最后一步处理本地 DateTimes,以某种很好的格式向用户展示它们。这种结构的另一个好处是,您可以以用户习惯的格式更准确地向用户显示干净的时区,而不是每次都在 TimeZoneInfo 类中四处寻找。

【讨论】:

    猜你喜欢
    • 2014-04-18
    • 1970-01-01
    • 1970-01-01
    • 2012-05-25
    • 1970-01-01
    • 2013-10-29
    • 2014-09-30
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多