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

时间:2018-04-26 04:59:30

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

以下代码检查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);

4 个答案:

答案 0 :(得分:2)

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的值,这会导致后续的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的切换发生在太平洋标准时间02:00,因此两个值仍处于标准时间内。

那么我们如何在没有黑客的情况下得到正确的答案呢?在转换为Windows文件时间之前,只需确保我们的输入值以DateTimeKind.Utc表示。 (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,因为Kind上没有DateTimeOffset,因此只有一种转换方法。

最后一件事。请注意,DateTime.AddHours(10)不符合DST差距。因此,当你在谈论上午8点的PDT时,由于春季前进的差距,实际上只有9个小时。实际经过的10小时是太平洋夏令时上午9点。如果您在添加10小时之前保留DateTimeOffset类型的值,则可以轻松纠正此问题。

答案 1 :(得分:0)

此代码工作正常。由于Daylight于2018年3月11日凌晨02:00在美国开始,因此对夏令时的回应是正确的。加拿大。

答案 2 :(得分:0)

更新

虽然我仍然支持我的第一个版本说明,但转换到当地时间会给出错误的结果测试,如果时间实际上是一天节省时间。我不确定为什么。 我在rextester上玩了你的代码试图找到解决方案。我发现的最好的一个非常麻烦 - 它涉及根据DateTime得到的DateTime实例创建一个新的FromFileTimeUtc实例:

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的构造函数中指定reminderstarttime,您将获得准确的结果。

请参阅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

答案 3 :(得分:0)

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