确实如此,但只能通过引擎盖下的一些黑魔法黑客行为。
当您查看 时DateTimeKind
,您将看到三个选项Unspecified
,Utc
和Local
。此信息被打包成内部 64 位表示的两位。由于有四个可能的值可以用两位表示,这就为第四种留下了空间。
正如 Jon Skeet在这篇博文中发现和描述的那样,确实存在隐藏的第四种!基本上,它是Local
但在解决模棱两可的时间时被区别对待。
当然,在.Net 之外,DateTime
与Local
kind 无论如何都不会往返。它被视为Unspecified
退货 - 除非您另有说明。我在这里写了一篇博客。更好的选择是DateTimeOffset
.
当然,这只是DateTime
.Net 中许多棘手的事情之一。Jon Skeet 的另一篇很棒的文章在这里讨论了其中的一些。
最好的解决方案是停止使用任何内置的日期和时间类型,而是熟悉Noda Time 。您可能仍然需要使用DateTime
或DateTimeOffset
与其他系统交互时,但您可以在内部使用 Noda Time,并让它为您完成所有转换。
附加信息
您询问了通过另一种格式(例如刻度或字符串)进行往返。
DateTime.Ticks
在 .Net 中不是一种很好的序列化格式,因为它不遵循单一的参考点。它们是自 0001 年 1 月 1 日午夜以来 100 纳秒间隔的整数。但它们与 UTC 无关 - 相反,它们与Kind
正在使用的一致。换句话说:
var utcNow = DateTime.UtcNow;
var now = utcNow.ToLocalTime();
var equal = utcNow.Ticks == now.Ticks; // false
将其与使用 1970 年 1 月 1 日参考点的 JavaScript 进行比较 - UTC 午夜。每当您获得多个刻度时,例如 with .getTime()
,它都会反映 UTC。您实际上无法通过简单的方法调用获得本地时间的刻度,因为它们在 JavaScript 中毫无意义。其他语言也是这样工作的。
此外,我们使用的公历直到 1582 年才生效,所以 1/1/0001 是它们的参考点,这很奇怪。1582 年之前的日期在我们目前的规模上没有意义,必须翻译。
字符串是传输日期和时间值的好方法,因为它们是人类可读的。但是您还应该确保它们是机器可读的,没有任何歧义。例如,不要使用类似 的值1/4/2013
,因为如果没有额外的文化信息,您将无法知道是 1 月 4 日还是 4 月 1 日。而是使用其中一种 ISO8601 格式。
当使用这些时DateTime
,您可以使用"o"
格式字符串,它可以往返类型。它为种类附加一个Z
,Utc
或为种类附加一个本地偏移量Local
。
var dt = new DateTime(2013,6,4,8,56,0); // Unspecified Kind
var iso = dt.ToString("o"); // 2013-06-04T08:56:00.0000000
var dt = DateTime.UtcNow; // Utc Kind
var iso = dt.ToString("o"); // 2013-06-04T15:56:00.0000000Z
var dt = DateTime.Now; // Local Kind
var iso = dt.ToString("o"); // 2013-06-04T08:56:00.0000000-07:00
从这种格式解析时,如果你没有偏移量,那么种类将是Unspecified
. 但是,如果您有一个Z
或任何偏移量,那么默认情况下该种类将是Local
. 它还将应用您提供的任何偏移量,以便结果是等效的本地时间。所以如果你想正确地应用它,你必须明确告诉它往返那种。
var dt = DateTime.Parse("2013-01-04T15:56:00.0000000Z");
var kind = dt.Kind; // Local - incorrect!
var s = dt.ToString("o"); // "2013-01-04T08:56:00.0000000-07:00" (ouch!)
反而:
var dt = DateTime.Parse("2013-01-04T15:56:00.0000000Z",
CultureInfo.InvariantCulture,
DateTimeStyles.RoundtripKind);
var kind = dt.Kind; // Utc - that's better.
var s = dt.ToString("o"); // "2013-01-04T15:56:00.0000000Z" (nice!)
当然,您最好使用DateTimeOffset
. 当你以 ISO8601 格式序列化它时,你总是得到一个完整的表示:
var dto = DateTimeOffset.Now;
var iso = dto.ToString("o"); // 2013-06-04T08:56:00.0000000-07:00
这种格式与RFC3339一致,RFC3339描述了 ISO8601 规范的这个配置文件,并且正在迅速成为在不同系统之间序列化时间戳的事实标准。恕我直言 - 您应该尽可能使用这种格式。它大大优于您在网络上常见的其他格式,例如 RFC1123。 以下是有关各种日期/时间格式的更多详细信息。
DateTimeOffset
值将始终往返,因为它们以序列化格式携带所有相关信息。所以会Unspecified
和Utc
种DateTime
。只是避开Local
各种DateTime
. 这些很容易让你陷入困境。
请回答?
再次阅读这篇文章,并意识到虽然我提供了很多细节,但我并没有直接回答你的问题。如果输入类型已经是您要转换的第一种类型,则测试将失败。让我们看一下两个测试条件:
someDateTime == someDateTime.ToUniversalTime().ToLocalTime()
如果原始值已经是Utc
kind,这将失败。
如果在 DST spring-forward 转换期间原始值在本地时区无效,则此测试也将失败。例如,2013-03-10 02:00:00
在美国太平洋时间不存在。但是,由于它不存在,您可能不会在数据中遇到它。所以它可能不是一个有效的测试条件。
someDateTime == someDateTime.ToLocalTime().ToUniversalTime()
如果原始值已经是Local
kind,这将失败。
另请注意,该Kind
物业不参与平等检查。因此,尽管其中任何一个的输入都可能是Unspecified
,但测试 1 的输出将始终有一个Local
种类,而测试 2 的输出将始终有一个Utc
种类 -但无论如何测试都会通过。