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

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

最佳答案

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 with .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 个实际经过的小时将是 PDT 上午 9 点。如果在 添加 10 小时之前根据 DateTimeOffset 类型保留值,则可以很容易地纠正此问题。

关于c# - IsDayLightSavingTime 方法为同一时区和同一时间返回不同的值,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50035120/

相关文章:

c# - .NET N 层架构 : What do I do about the Model objects?

c# - 在 NetworkCredentials 中提供用户名和密码时,.net 中的 HttpClient 发出 2 个请求

task-parallel-library - 将CancellationToken传递给Task.Factory.StartNew()的目的是什么

用于 HyperTable 的 C# 库

c# - ASP.Net Identity - 不同应用程序的中央用户数据库?

.net - 如何在现有控件下放置一个新面板?

.net - 使用 X509Certificate2 和 ECC 公钥加载证书

c# - 有没有办法在没有 return 语句的情况下在函数中返回默认值?

c# - 如何使用 C# 读取 gridview 中动态添加的文本框值?

c# - C# 7.0 是否适用于 .NET 4.5?