C# IsDayLightSavingTime方法为同一时区和同一时间返回不同的值

C# IsDayLightSavingTime方法为同一时区和同一时间返回不同的值,c#,.net,.net-4.5,dst,C#,.net,.net 4.5,Dst,以下代码用于检查DST中的特定时间,或不返回正常日期时间和从filetime获得的相同时间的不同值: var tzInfo = TimeZoneInfo.FindSystemTimeZoneById("Pacific Standard Time"); var reminderstarttime = new DateTime(2018, 3, 10, 22, 0, 0); var referencetime = reminderstarttime.AddHours(10); // Referen

以下代码用于检查DST中的特定时间,或不返回正常日期时间和从filetime获得的相同时间的不同值:

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);

此代码运行良好。之所以对夏令时做出这样的回应,是因为美国和加拿大的夏令时于2018年3月11日凌晨2:00开始。

更新 虽然我仍然坚持我的第一个版本的解释,但如果转换为本地时间实际上是一个白天节省的时间,那么将给出错误的测试结果。我不知道确切的原因。 我在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);
我承认这可能更像是一个解决办法,而不是一个解决方案,但我认为我在这个问题上花了更多的时间,我已经负担得起了。也许有人对日期时间、文件时间和时区有更多的经验,可以对此有所了解

第一版 问题是
referencetime
Kind
属性是
DateTimeKind.Unspecified
。如果在
提醒开始时间
的构造函数中指定
DateTimeKind.Local
,则会得到准确的结果

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

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

referencetimeFromUtc.Kind是Utc,因此不在DST中。查看来源:

佐哈尔的说法基本正确。关键点在于,
DateTime.ToFileTimeUtc
,与处理
DateTime
的许多方法一样,依赖于与值关联的
种类。当传递
DateTimeKind.Unspecified
时,此特定方法假定输入已经是UTC。然而,在代码中,您正在创建这些值,就好像它们是根据给定的时区创建的一样

让我们把注意力集中在罪犯身上:

var reminderStartTimeToUtc = (ulong)reminderstarttime.ToFileTimeUtc();
var referenceTimeToUtc = (ulong)referencetime.ToFileTimeUtc();
由于
提醒开始时间
引用时间
都具有
种类==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
的值,这将取消后续DST检查:

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发生在PST 02:00,因此这两个值仍处于标准时间

那么,我们如何在没有黑客攻击的情况下得到正确的答案呢?在将文件转换为Windows文件时间之前,只需确保输入值为
DateTimeKind.Utc
。(
DateTimeKind.Local
也可以,但这里不需要涉及本地时区)

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

请注意,这些方法的措辞有些混乱
DateTime.ToFileTimeUtc
表示您正在转换为文件时间,并且使用
.Kind==DateTimeKind输入
DateTime
。未指定的
将被视为是
DateTimeKind.Utc
。另一种方法,
DateTime.ToFileTime
Unspecified
类型视为
Local
。但它们都以相同的方式处理
Utc
Local
类型,并且它们都生成一个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间隔。因此,当您谈论PDT上午8点时,由于弹簧前进间隙,实际只经过了9个小时。10个实际运行小时为太平洋夏令时上午9点。如果在添加10个小时之前,将值保留为
DateTimeOffset
类型,则可以很容易地更正此问题。

您误解了这个问题。OP错误地认为
referencetime
referencetimefromtc
是同一时间,但正如我在回答中所示,它们不是。根据,这是错误的。如果datetime.Kind是utc,则在返回答案之前,
IsDaylightSavingTime
将日期时间转换为本地时间。Yu Xie,您指的是时区文档。我在这里使用TimeZoneInfo。文档:但这里的结果是否错误:AddHours(10)给出的DateTime为2018年11月3日上午8:00:00,这是太平洋标准时间的DST时间?这是一个问题。我正在查。但是我想,你在回答中说的是正确的。根据实现注释:FromFileTimeUtc将DateTimekind作为UTC返回datetime对象。因此,根据IsDayLightSavingTime实施,将首先将其转换为PST,然后检查DST。2018年11月3日上午8:00:00转换为UTC时,将是2018年10月3日上午1:00:00,这不确定为什么IsDayLightSavingTime的实施取决于种类属性。默认情况下,它应将给定时间视为所请求时区中的时间。不是吗?我已经编辑了我的答案。然而,正如我所写的,也许som
// 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();