【问题标题】:IsDayLightSavingTime method returns different values for same timezone and same timeIsDayLightSavingTime 方法返回相同时区和相同时间的不同值
【发布时间】:2018-10-06 16:32:00
【问题描述】:

以下代码检查 DST 中的特定时间,或者在正常日期时间和从文件时间获得的同一时间不返回不同的值:

var tzInfo = TimeZoneInfo.FindSystemTimeZoneById("Pacific Standard Time");
var reminderstarttime = new DateTime(2018, 3, 10, 22, 0, 0);
var referencetime = reminderstarttime.AddHours(10);  // ReferencedTime is in DST;

var isRemDstWithNormal = tzInfo.IsDaylightSavingTime(reminderstarttime);
var isRefDstWithNormal = tzInfo.IsDaylightSavingTime(referencetime);

var reminderStartTimeToUtc = (ulong)reminderstarttime.ToFileTimeUtc();
var referenceTimeToUtc = (ulong)referencetime.ToFileTimeUtc();

var reminderStartTimeFromUtc = DateTime.FromFileTimeUtc((long)reminderStartTimeToUtc);
var referencetimeFromUtc = DateTime.FromFileTimeUtc((long)referenceTimeToUtc);

var isRemDSTFromFileTime = tzInfo.IsDaylightSavingTime(reminderStartTimeFromUtc);
var isRefTimeDSTFromFileTime = tzInfo.IsDaylightSavingTime(referencetimeFromUtc);

Console.WriteLine("isRemDstWithNormal: " + isRemDstWithNormal + 
                 " isRefDstWithNormal: " + isRefDstWithNormal + 
                 " isRemDSTFromFileTime " + isRemDSTFromFileTime + 
                 " isRefTimeDSTFromFileTime: " + isRefTimeDSTFromFileTime);

【问题讨论】:

    标签: c# .net .net-4.5 dst


    【解决方案1】:

    Zohar 基本上是正确的。关键是DateTime.ToFileTimeUtc,就像许多在DateTime 上工作的方法一样,依赖于与值关联的Kind。当DateTimeKind.Unspecified 被传递时,这个特定的方法假定输入已经是UTC。但是,在您的代码中,您正在创建这些值,就好像它们是根据给定的时区一样。

    让我们把罪魁祸首归零:

    var reminderStartTimeToUtc = (ulong)reminderstarttime.ToFileTimeUtc();
    var referenceTimeToUtc = (ulong)referencetime.ToFileTimeUtc();
    

    由于reminderstarttimereferencetime 都有Kind == DateTimeKind.Unspecified,因此它们生成的文件时间值不正确。具体来说:

    reminderStartTimeToUtc:  131651928000000000
                 we wanted:  131652216000000000
                difference:       -288000000000  = -8 hours
    
        referenceTimeToUtc:  131652288000000000
                 we wanted:  131652540000000000
                difference:       -252000000000  = -7 hours
    

    如您所见,它们的值因每个日期与 UTC 的差异而不同。

    使用DateTime.FromFileTimeUtc 将它们转换回您的代码中会返回确实 具有DateTimeKind.Utc 的值,这会引发后续的夏令时检查:

    reminderStartTimeFromUtc:  2018-03-10 22:00:00 UTC
      which is equivalent to:  2018-03-10 14:00:00 PST (UTC-8)
                   we wanted:  2018-03-10 22:00:00 PST (UTC-8)
    
        referencetimeFromUtc:  2018-03-11 08:00:00 UTC
      which is equivalent to:  2018-03-11 00:00:00 PST (UTC-8)
                   we wanted:  2018-03-11 08:00:00 PDT (UTC-8)
    

    请注意,从 PST 到 PDT 的切换发生在 02:00 PST,因此这两个值仍然是标准时间。

    那么我们如何在没有 hack 的情况下获得正确的 this?只需确保我们的输入值是DateTimeKind.Utc,然后再将其转换为 Windows 文件时间。 (DateTimeKind.Local 也可以,但这里不需要涉及本地时区)

    // First convert the DateTime values from their unspecified zone-specific times to UTC
    var reminderStartTimeUtc = TimeZoneInfo.ConvertTimeToUtc(reminderstarttime, tzInfo);
    var referenceTimeUtc = TimeZoneInfo.ConvertTimeToUtc(referencetime, tzInfo);
    
    // Then convert THOSE values to file-times.
    var reminderStartTimeToUtc = (ulong)reminderStartTimeUtc.ToFileTimeUtc();
    var referenceTimeToUtc = (ulong)referenceTimeUtc.ToFileTimeUtc();
    

    其余代码将按原样正确执行,您将获得预期的结果。

    请注意,这些方法的措辞有些混乱。 DateTime.ToFileTimeUtc 表示您正在转换为文件时间,输入DateTime.Kind == DateTimeKind.Unspecified 将被视为DateTimeKind.Utc。另一种方法DateTime.ToFileTimeUnspecified 种类视为Local。但它们对待UtcLocal 种类相同,并且它们都产生Windows 文件时间,它本质上是基于UTC 的。

    作为上述方法的替代方案,您可以改用DateTimeOffset.ToFileTime。在转换为文件时间期间将正确考虑偏移量。

    // construct a DateTimeOffset for each value
    var reminderStartTimeDto = new DateTimeOffset(reminderstarttime, tzInfo.GetUtcOffset(reminderstarttime));
    var referencetimeDto = new DateTimeOffset(referencetime, tzInfo.GetUtcOffset(referencetime));
    
    // then just convert them to file times
    var reminderStartTimeAsFileTime = reminderStartTimeDto.ToFileTime();
    var referenceTimeAsFileTime = referencetimeDto.ToFileTime();
    

    注意这里没有ToFileTimeUtc,因为DateTimeOffset上没有Kind,所以只有一种方法可以转换它。

    最后一件事。请注意,DateTime.AddHours(10) 不遵守 DST 间隔。因此,当您谈论太平洋夏令时间上午 8 点时,实际上只过去了 9 个小时,这是由于弹簧向前间隙。 10 个实际经过的小时将是太平洋夏令时间上午 9 点。如果您在添加 10 小时之前 保留 DateTimeOffset 类型的值,则可以轻松纠正此问题。

    【讨论】:

    • 最后一段说得好!我以前从未想过那个...幸好我知道该向谁寻求帮助 :-) +1。
    【解决方案2】:

    此代码运行良好。这是因为夏令时在美国和加拿大于 2018 年 3 月 11 日凌晨 02:00 开始。

    【讨论】:

    • 你误解了这个问题。 OP 错误地认为 referencetimereferencetimeFromUtc 是同一时间,但正如我在回答中所表明的那样,它们不是。
    【解决方案3】:

    referencetimeFromUtc.Kind 是 Utc,所以它不在 DST 中。查看来源:https://referencesource.microsoft.com/#mscorlib/system/timezone.cs,ccfb563c9867b5d0

    【讨论】:

    【解决方案4】:

    更新

    虽然我仍然坚持我的第一个版本解释,但如果时间实际上是夏令时,转换为本地时间会给出错误的结果测试。我不确定究竟是为什么。 我在 rextester 上玩过你的代码,试图想出一个解决方案。我发现的最好的一个非常麻烦 - 它涉及基于您从FromFileTimeUtc 获得的DateTime 实例创建一个新的DateTime 实例:

    var tzInfo = TimeZoneInfo.FindSystemTimeZoneById("Pacific Standard Time");
    var reminderstarttime = new DateTime(2018, 3, 10, 22, 0, 0);
    var referencetime = reminderstarttime.AddHours(10);  // ReferencedTime is in DST;
    
    var isRemDstWithNormal = tzInfo.IsDaylightSavingTime(reminderstarttime);
    var isRefDstWithNormal = tzInfo.IsDaylightSavingTime(referencetime);
    
    var reminderStartTimeToUtc = reminderstarttime.ToFileTimeUtc();
    var referenceTimeToUtc = referencetime.ToFileTimeUtc();
    
    var reminderStartTimeFromUtc = DateTime.FromFileTimeUtc(reminderStartTimeToUtc);
    var referencetimeFromUtc = DateTime.FromFileTimeUtc(referenceTimeToUtc);
    
    var isRemDSTFromFileTime = tzInfo.IsDaylightSavingTime(reminderStartTimeFromUtc);
    var isRefTimeDSTFromFileTime = tzInfo.IsDaylightSavingTime(referencetimeFromUtc);
    
    var referenceTimeFromFileTimeUnspecified = new DateTime(referencetimeFromUtc.Ticks);
    var isReferenceTimeFromFileTimeUnspecifiedDTS =  tzInfo.IsDaylightSavingTime(referenceTimeFromFileTimeUnspecified);
    
    Console.WriteLine("isRemDstWithNormal: " + isRemDstWithNormal + 
                     "\nisRefDstWithNormal: " + isRefDstWithNormal + 
                     "\nisRemDSTFromFileTime " + isRemDSTFromFileTime + 
                     "\nisRefTimeDSTFromFileTime: " + isRefTimeDSTFromFileTime +
                     "\nisReferenceTimeFromFileTimeUnspecifiedDTS: "+ isReferenceTimeFromFileTimeUnspecifiedDTS);
    

    我承认这可能更像是一种解决方法而不是解决方案,但我认为我已经在这个问题上花费了更多时间,然后我已经负担得起了。也许对日期时间、文件时间和时区有更多经验的人可以对此有所了解。

    第一版

    问题是您的referencetimeKind 属性是DateTimeKind.Unspecified。如果你在reminderstarttime的构造函数中指定DateTimeKind.Local,你会得到准确的结果。

    参见DateTime.ToFileTimeUtc方法文档中的备注部分:

    ToFileTimeUtc 方法使用 Kind 属性来确定当前 DateTime 对象是本地时间、UTC 时间、还是被视为 UTC 时间的未指定类型的时间。 如果是本地时间,它在转换为 Windows 文件时间之前将时间转换为 UTC。

    (强调我的)

    这是我的测试代码:

    var tzInfo = TimeZoneInfo.FindSystemTimeZoneById("Pacific Standard Time");
    // This is the only change, except replacing the space with \n in the Console.WriteLine:
    //var reminderstarttime = new DateTime(2018, 3, 10, 22, 0, 0);
    var reminderstarttime = new DateTime(2018, 3, 10, 22, 0, 0, DateTimeKind.Local);
    var referencetime = reminderstarttime.AddHours(10);  // ReferencedTime is in DST;
    
    var isRemDstWithNormal = tzInfo.IsDaylightSavingTime(reminderstarttime);
    var isRefDstWithNormal = tzInfo.IsDaylightSavingTime(referencetime);
    
    var reminderStartTimeToUtc = reminderstarttime.ToFileTimeUtc();
    var referenceTimeToUtc = referencetime.ToFileTimeUtc();
    
    var reminderStartTimeFromUtc = DateTime.FromFileTimeUtc(reminderStartTimeToUtc);
    var referencetimeFromUtc = DateTime.FromFileTimeUtc(referenceTimeToUtc);
    
    var isRemDSTFromFileTime = tzInfo.IsDaylightSavingTime(reminderStartTimeFromUtc);
    var isRefTimeDSTFromFileTime = tzInfo.IsDaylightSavingTime(referencetimeFromUtc);
    
    Console.WriteLine("isRemDstWithNormal: " + isRemDstWithNormal + 
                     "\nisRefDstWithNormal: " + isRefDstWithNormal + 
                     "\nisRemDSTFromFileTime " + isRemDSTFromFileTime + 
                     "\nisRefTimeDSTFromFileTime: " + isRefTimeDSTFromFileTime);
    

    结果:

    isRemDstWithNormal: False
    isRefDstWithNormal: False
    isRemDSTFromFileTime False
    isRefTimeDSTFromFileTime: False
    

    【讨论】:

    • 但是这里的结果不是错误的吗:AddHours(10) 将 DateTime 提供为 3/11/2018 8:00:00 AM,这是太平洋标准时间的 DST 时间?
    • 这是个问题。我现在正在检查。
    • 但我认为,您在答案中所说的是正确的。根据实现 cmets :referencesource.microsoft.com/#mscorlib/system/… FromFileTimeUtc 使返回的日期时间对象的 DateTimekind 为 UTC。因此,根据 IsDayLightSavingTime 实现,这将首先转换为 PST,然后检查 DST。 2018 年 3 月 11 日上午 8:00:00 转换为 UTC 将是 2018 年 3 月 10 日上午 1:00:00,这不是 DST
    • 不知道为什么 IsDayLightSavingTime 的实现依赖于 Kind 属性。默认情况下,它应该将给定时间视为询问时区内的时间。不是吗?
    • 我已经编辑了我的答案。然而,正如我所写的,也许在框架的这一部分有更多经验的人可以对此有所了解。所以打电话给@MattJohnson:我想你可能会比我更好地回答这个问题。想试一试吗?
    猜你喜欢
    • 1970-01-01
    • 2020-01-29
    • 1970-01-01
    • 1970-01-01
    • 2012-02-04
    • 1970-01-01
    • 1970-01-01
    • 2017-09-12
    • 1970-01-01
    相关资源
    最近更新 更多