44

我刚刚注意到 DateTime 比较似乎是一个荒谬的缺陷。

DateTime d = DateTime.Now;
DateTime dUtc = d.ToUniversalTime();

d == dUtc; // false
d.Equals(dUtc); //false
DateTime.Compare(d, dUtc) == 0; // false

如果一个是 DateTimeKind.Local 和一个是 DateTimeKind.UTC,则 DateTimes 上的所有比较操作似乎都无法进行任何类型的智能转换。除了始终将比较中涉及的两者都转换为 utc 时间之外,还有更好的方法来可靠地比较 DateTimes 吗?

4

2 回答 2

50

当您调用.Equalor.Compare时,会在内部.InternalTicks比较值,即 aulong没有前两位。这个字段是不相等的,因为它已经被调整了几个小时来表示世界时的时间:当你调用 时ToUniversalTime(),它会以当前系统的本地时区设置的偏移量来调整时间。

您应该这样看:DateTime 对象表示未命名时区中的时间,但不是通用时间加时区。时区是本地(系统的时区)或 UTC。您可能会认为这是缺少 DateTime 类,但从历史上看,它已被实现为“自 1970 年以来的滴答数”并且不包含时区信息。

转换为另一个 UTC时,时间是——而且应该是——调整的。这可能是微软选择使用方法而不是属性的原因,以强调在转换为 UTC 时会采取行动。

最初我在这里写的是结构是比较的,标志System.DateTime.Kind是不同的。这是不正确的:不同的是刻度的数量:

t1.Ticks == t2.Ticks;       // false
t1.Ticks.Equals(t2.Ticks);  // false

为了安全地比较两个日期,您可以将它们转换为相同的日期。如果您在比较之前将任何日期转换为通用时间,您将获得您所追求的结果:

DateTime t1 = DateTime.Now;
DateTime t2 = someOtherTime;
DateTime.Compare(t1.ToUniversalTime(), t2.ToUniversalTime());  // 0
DateTime.Equals(t1.ToUniversalTime(), t2.ToUniversalTime());  // true

在不更改本地时间的情况下转换为 UTC 时间

除了转换为 UTC(并且在此过程中保持时间相同,但滴答数不同),您还可以覆盖并将DateTimeKind其设置为 UTC(这会更改时间,因为它现在是 UTC,但它比较相等,因为刻度数相等)。

var t1 = DateTime.Now
var t2 = DateTime.SpecifyKind(t1, DateTimeKind.Utc)
var areEqual = t1 == t2   // true
var stillEqual = t1.Equals(t2) // true

我想这DateTime是一种罕见的类型,可以按位不相等,但比较相等,或者可以按位相等(时间部分)并比较不相等。

.NET 6 中的变化

在 .NET 6.0 中,我们现在拥有TimeOnlyDateOnly. 您可以使用这些来存储“只是一天中的时间”,或者“只是一年中的日期”。将这些组合在一个结构中,您将拥有一个 Date & Time 结构,而没有原始DateTime.

备择方案

在 .NET 中很难使用DateTime、 、 闰秒、日历、时区转换、持续时间等正常工作。TimeZoneInfo我个人更喜欢NodaTimeJon Skeet 的作品,它以一种有意义且明确的方式将控制权交还给了程序员。

Jon Skeet 的这篇富有洞察力的文章深入解释了程序员在尝试绕过所有 DateTime 问题时可能面临的麻烦,而只是将所有内容存储在 UTC 中。

来源的背景信息

如果您检查.NET 源代码中DateTime结构,您会发现一个注释解释了最初(在 .NET 1.0 中)这DateTime只是刻度数,但后来他们添加了存储它是通用还是本地的能力时间。但是,如果您进行序列化,则此信息将丢失。

这是源代码中的注释:

    // This value type represents a date and time.  Every DateTime
    // object has a private field (Ticks) of type Int64 that stores the
    // date and time as the number of 100 nanosecond intervals since
    // 12:00 AM January 1, year 1 A.D. in the proleptic Gregorian Calendar.
    //
    // Starting from V2.0, DateTime also stored some context about its time
    // zone in the form of a 3-state value representing Unspecified, Utc or
    // Local. This is stored in the two top bits of the 64-bit numeric value
    // with the remainder of the bits storing the tick count. This information
    // is only used during time zone conversions and is not part of the
    // identity of the DateTime. Thus, operations like Compare and Equals
    // ignore this state. This is to stay compatible with earlier behavior
    // and performance characteristics and to avoid forcing  people into dealing
    // with the effects of daylight savings. Note, that this has little effect
    // on how the DateTime works except in a context where its specific time
    // zone is needed, such as during conversions and some parsing and formatting
    // cases.
于 2011-08-03T17:24:23.367 回答
6

为了解决这个问题,我创建了我自己的包含 DateTime 和 TimeZone 的 DateTime 对象(我们称之为 SmartDateTime)。在使用原始 DateTime 运算符进行比较之前,我会覆盖所有运算符,例如 == 和 Compare 并转换为 UTC。

于 2011-08-03T17:24:56.817 回答