Unix 时间戳:使用 ToUnixTimeMilliseconds 和 TimeSpan.TotalMilliseconds 的区别

Unix Timestamp: Difference between using ToUnixTimeMilliseconds and TimeSpan.TotalMilliseconds

我正在将日期时间转换为 Unix 时间。据我了解,这两种方式应该 return 相同的结果。

选项 1

DateTime dtfoo = new DateTime(2010, 10, 20);
DateTimeOffset dtfoo2 = new DateTimeOffset(dtfoo).ToUniversalTime();
long afoo = dtfoo2.ToUnixTimeMilliseconds();

选项 2

DateTime dtfoo = new DateTime(2010, 10, 20);
DateTimeOffset dtfoo2 = new DateTimeOffset(dtfoo).ToUniversalTime();
long afoo = (Int64)(dtfoo2.Subtract(new DateTime(1970, 1, 1))).TotalMilliseconds;

选项 1 returns 1287525600000 和选项 2 returns 1287529200000.

为什么我得到不同的结果?

差异可能是因为 ToUnixTimeMilliseconds 没有考虑闰秒。

这是文档中的内容:

https://docs.microsoft.com/en-us/dotnet/api/system.datetimeoffset.tounixtimemilliseconds?view=netframework-4.7.2

Unix time represents the number of seconds that have elapsed since 1970-01-01T00:00:00Z (January 1, 1970, at 12:00 AM UTC). It does not take leap seconds into account. This method returns the number of milliseconds in Unix time.

因为 TimeSpan.TotalMilliseconds 没有谈到这一点。

https://docs.microsoft.com/en-us/dotnet/api/system.timespan.totalmilliseconds?view=netframework-4.7.2

This property converts the value of this instance from ticks to milliseconds. This number might include whole and fractional milliseconds.

注意:我在UTC+9,你的问题根源来自时区偏移,所以理解我看到的unix时间可能与你的略有不同。


区别在于您处理日期对象的方式。根据结果​​差异,我假设您的时区是 CET(或者您使用的是 rextester,我相信它在德国)。

考虑以下代码:

var dtfoo = new DateTime(2010, 10, 20);
var dtfoo2 = new DateTimeOffset(dtfoo);
var dtfoo3 = dtfoo2.ToUniversalTime();
  1. 第一行创建一个 DateTime 和一个 DateTimeKind of "Unspecified"
  2. 第二行由此创建一个 DateTimeOffset 对象。因为 DateTimeKind 未指定,所以使用与 UTC 的系统时间偏移量。
  3. 第三行将此日期转换为 UTC。

为 #2 引用 documentation

If the value of DateTime.Kind is DateTimeKind.Local or DateTimeKind.Unspecified, the DateTime property of the new instance is set equal to dateTime, and the Offset property is set equal to the offset of the local system's current time zone.

现在让我们写出 1-3 的往返格式日期字符串:

2010-10-20T00:00:00.0000000
2010-10-20T00:00:00.0000000+09:00
2010-10-19T15:00:00.0000000+00:00

我使用的是 UTC+9,所以 DateTimeOffset 是用 +9h 的偏移量正确创建的。将其转换为 universal 将我们带到 19 日下午 3 点。不幸的是,这导致 .ToUnixTimeMilliseconds() 的输出为 1287500400000,即 2010-10-19T15:00:00Z。该值已取决于机器的时区。

那么,现在让我们来看看你的第二个例子:

DateTime dtfoo = new DateTime(2010, 10, 20);
DateTimeOffset dtfoo2 = new DateTimeOffset(dtfoo).ToUniversalTime();
long afoo = (Int64)(dtfoo2.Subtract(new DateTime(1970, 1, 1))).TotalMilliseconds;

好的,让我们把它分成不同的部分,这样我们就可以看到系统认为它们代表的时间(记住我在 UTC+9 中):

  1. new DateTime(2010, 10, 20).ToString("o") - 2010-10-20T00:00:00.0000000
  2. new DateTimeOffset(dtfoo).ToString("o") - 2010-10-20T00:00:00.0000000+09:00
  3. new DateTimeOffset(dtfoo).ToUniversalTime() - 2010-10-19T15:00:00.0000000+00:00
  4. new DateTime(1970, 1, 1).ToString("o") - 1970-01-01T00:00:00.0000000

所以你有效地执行了这个计算:

(DateTimeOffset.Parse("2010-10-19T15:00:00.0000000+00:00") - DateTime.Parse("1970-01-01T00:00:00.0000000")).TotalMilliseconds

这输出 1287532800000,等于 2010-10-20T00:00:00Z。由于减法的完成方式,这会为您提供正确的结果:

  1. DateTimeimplicitly castDateTimeOffset,相当于 new DateTimeOffset(DateTime.Parse("1970-01-01T00:00:00.000000")) - 这意味着两个输入日期都经过了相同的时区更改。
  2. 通过调用 DateTimeOffset.UtcDateTime 属性.
  3. subtraction 的两个日期都转换为 DateTime 对象

那么我们如何修复您的原始示例?我们可以通过在构造 DateTimeOffset:

时指定偏移量来将本地时区偏移量从等式中取出
DateTime dtfoo = new DateTime(2010, 10, 20);
DateTimeOffset dtfoo2 = new DateTimeOffset(dtfoo, TimeSpan.Zero).ToUniversalTime();
long afoo = dtfoo2.ToUnixTimeMilliseconds();

这现在为我们提供了与之前测试中相同的值:1287532800000。如果我们使用 DateTimeOffset.Parse 来简化它,我们应该确认我们在正确的轨道上:

Console.WriteLine(DateTimeOffset.Parse("2010-10-20T00:00:00Z").ToUnixTimeMilliseconds());

我们可以看到这也输出了 1287532800000.

因此,总而言之,您的问题源于 DateTimeOffset(datetime) 构造函数如何使用 DateTimeKind UnspecifiedLocal 处理日期。它会根据您机器的时区扭曲您生成的世界时间。这会导致不正确的 unix 时间偏移。要解决它,只需按照我上面描述的一种方式创建您的 DateTimeOffset