.net 安全地比较本地和通用DateTime

sshcrbum  于 2022-12-24  发布在  .NET
关注(0)|答案(2)|浏览(144)

我刚刚注意到日期时间比较的一个可笑的缺陷。

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的更好方法吗?

dsekswqp

dsekswqp1#

当你调用.Equal.Compare时,内部比较的是.InternalTicks,它是一个没有前两位的ulong,这个字段是 * 不相等的 *,因为它被调整了几个小时来表示世界时:当你调用ToUniversalTime()时,它会用当前系统的本地时区设置的偏移量来调整时间。
你应该这样看:DateTime对象表示一个未命名时区中的 time,但不是世界时加时区。时区可以是Local(系统的时区)或UTC。您可能认为这是缺少DateTime类,但历史上它一直被实现为“自1970年以来的滴答数”,并且不包含时区信息。
当 * 转换 * 到另一个时区时,时间是--而且应该是--调整的。这可能就是为什么微软选择使用 * 方法 * 而不是属性,以强调转换到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时间而不更改本地时间

除了将DateTimeKind * 转换 * 为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,你可以用它们来存储 “仅仅是一天中的时间”,of “仅仅是一年中的日期”,把它们组合在一个结构体中,你就得到了一个没有原始DateTime的历史麻烦的Date & Time结构体。

备选方案

在. NET中,很难正确地使用DateTimeTimeZoneInfo、闰秒、日历、时区转换、持续时间等。我个人更喜欢Jon Skeet的NodaTime,它以一种有意义、明确的方式将控制权交还给程序员。
This insightful post by Jon Skeet explains非常深入地介绍了程序员在尝试规避所有日期时间问题时可能面临的麻烦,而只是将所有内容存储在UTC中。

来自源的背景信息

如果你检查the DateTime struct in the .NET source,你会发现一个注解,解释了DateTime最初(在.NET 1.0中)只是滴答数,但后来他们增加了存储它是通用时间还是本地时间的能力。
这是来源中的注解:

// 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.
ss2ws0br

ss2ws0br2#

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

相关问题