【问题标题】:Why does TimeZoneInfo.IsValidTime() give unexpected result for DateTimeKind.Local?为什么 TimeZoneInfo.IsValidTime() 会为 DateTimeKind.Local 提供意外结果?
【发布时间】:2023-08-22 02:13:01
【问题描述】:

我对 TimeZoneInfo.IsValidTime() 的行为感到惊讶,因为当 DateTime 设置为 DateTimeKind.Local 时,它无法正常工作

[Fact]
public void DateTimeInvalidForTimeZone()
{
    TimeZoneInfo timeZone = TimeZoneInfo.FindSystemTimeZoneById("Central European Standard Time");

    var testTimeUnspec = new DateTime(2020, 3, 29, 02, 01, 0, DateTimeKind.Unspecified);
    var testTimeLocal = new DateTime(2020, 3, 29, 02, 01, 0, DateTimeKind.Local);
    var testTimeUtc = new DateTime(2020, 3, 29, 02, 01, 0, DateTimeKind.Utc);

    Assert.False(timeZone.IsInvalidTime(testTimeUtc));     //as anticipated - UTC so cannot be invalid
    Assert.True(timeZone.IsInvalidTime(testTimeUnspec));   //as anticipated - the time is invalid
    Assert.False(timeZone.IsInvalidTime(testTimeLocal));   //unexpected - the time is invalid
}

注意:中欧标准时间在 2020 年 3 月 29 日凌晨 02:00 转换为夏令时,因此按照当地时间的时间顺序为 01:59:58、01:59:59、03:00:00、03 :00:01。因此,02:00:00 到 02:59:59 之间的所有当地时间都是无效的。

Microsoft Documentation 解释:

  • 对于非本地的 DateTimeKind.Local 和 TimeZoneInfo(即与计算机的时区不同)IsValidTime(DateTime) 将 DateTime 转换为 TimeZoneInfo 对象的时间并返回 false .

这表明 DateTimeKind.Local 的 DateTime 对象将始终有效,这不是我所期望或想要的。谁能解释微软实施背后的逻辑?另一个考虑 Nodatime?

的理由

【问题讨论】:

  • 您的当地时间无效。你期望结果是什么? DateTime 作为 UTF 格式的数字存储在计算机(和网络)中。然后,当日期显示为字符串时,Net 应用计算机的时区设置作为默认格式。当在 Net 中将日期从字符串解析为数字时,默认采用本地时间并转换为 UTF,除非字符串包含时区。
  • 我希望 IsInvalidTime() 在传递无效日期时返回 TRUE,在传递有效日期时返回 FALSE。在我上次的测试中没有这样做。因此我的问题。我的测试仅在最后一次测试的 Assert.False 时通过。
  • 我猜 New DateTime 正在转换为 UTC。一旦它被转换,它就不再无效。假设有人在时间转换时正在工作 2:30。这个人忘记换手表了。 Net 库只是将时间转换为 UTC 而没有任何错误并保存结果。一旦它被保存,它就不再是错误的了。正如我所说,时间始终以 UTC 格式存储。
  • 如何将无效的 DateTime 转换为 UTC?如果您在 2020 年 3 月 29 日凌晨 1 点 59 分在华沙工作,并记录了它的有效时间,那么您可以将其转换为 UTC (00:59)。两分钟后,华沙的凌晨 3:01 也是有效的,因此您可以将其转换为 UTC (01:01)。但是,如果您错误地创建了一个 TimeDate(2020, 3, 29, 2, 1, 0) 来表示华沙当地时间,则无法将其转换为 UTC,因为它是无效的。
  • 时间并不总是以 UTC 格式存储,但我同意这样做是个好主意。 DateTime 对象在其 Kind 属性之外没有关于其时区的信息 - 本质上是一个表明它是否为 UTC 的标志。因此,您可以创建和存储在大多数时区都有效的 TimeDate(2020, 3, 29, 2, 1, 0)。但是,如果它与华沙当地时间有关,则无效。这就是为什么我需要类似 timeZone.IsInvalidTime() 的东西 - 即传递的 DateTime 在这个特定时区是否有效?

标签: c# timezone


【解决方案1】:

正如您所指出的,the docs for TimeZoneInfo.IsInvalidTime 解释说,当TimeZoneInfo 不是本地时区时,返回false 是记录在案的行为,并且无论时间在本地时区或本地时区是否有效都是正确的属于TimeZoneInfo 对象的那个。实际上,在这种情况下,the implementation 只会返回 false

如果你想知道当地时间是否有效,那么你需要使用TimeZoneInfo.Local时区,而不是通过id获取的时区。该命名时区是否是本地时区并不重要,如果您正在测试 DateTimeKind.Local 值,则需要明确使用 TimeZoneInfo.Local

换句话说,如果您的计算机的系统本地时区确实是"Central European Standard Time"(假设“自动调整夏令时”已打开),那么以下将按预期返回True .

TimeZoneInfo.Local.IsInvalidTime(new DateTime(2020, 3, 29, 02, 01, 0, DateTimeKind.Local))

【讨论】:

  • 我的问题是我不知道计算机系统本地时区的设置,因为我的代码在 Azure 上运行,因此可能会被移动。我想要为用户选择的任何 TimeZone 初始化 TimeZoneInfo,然后计算他在文本框中给出的时间是否有效。似乎是一个合理的要求!
  • 如果您的代码未在物理设备(台式机、移动设备等)上运行,则不应使用DateTimeKind.Local。在您刚刚描述的场景中,您应该使用DateTimeKind.Unspecified。如果您使用DateTime.ParseDateTime.ParseExactConvert.ToDateTime 解析字符串,它将已经具有DateTimeKind.Unspecified,除非它们是字符串中的偏移量,在这种情况下您将得到@ 987654340@。你可以通过DateTime.SpecifyKind 克服这个问题。
  • 关于野田时间 - 如果你可以使用它,那么你应该。它们的关键区别在于,它需要您更仔细地考虑这些边缘情况,内置类型将在这些情况下做出假设。他们都可以完成工作,但假设可能会受到伤害。 (例如:如果输入字符串中有偏移量,则种类会发生变化。)