【问题标题】:Keeping date/time values consistent when converting UNIX timestamps to .Net DateTime and back将 UNIX 时间戳转换为 .Net DateTime 并返回时保持日期/时间值一致
【发布时间】:2015-09-28 12:08:01
【问题描述】:

我需要将 unix TimeStamp 转换为 .Net DateTIme 值,反之亦然。 这没问题,但我需要将转换后的值存储为 Int64(这意味着我会丢失小数位)。

丢失小数位意味着我可以得到原始值和转换值之间的差异。 如果可以的话,我会更改实现并将值存储为 Double 而不是 Int64……但遗憾的是我不能(规范等)。

编辑: 我需要将值存储为 Int64,表示自 1970 年以来的秒数(unix 时间戳)

我想我已经找到了解决这个问题的方法,但我不确定它是否 100% 有效。 (它在我的测试中 100% 有效)。在转换为 int 接缝之前将 Double 值四舍五入以解决问题。

这能解决问题吗?

这是我的代码:

[Test]
public void ConvertTest()
{
    const Int64 orignalUnixValue = 252000596321;
    var referenceTimeZero = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);

    //Test 1
    var convertedDateTime = referenceTimeZero.AddSeconds(orignalUnixValue);
    var convertedUnixValue = (convertedDateTime - referenceTimeZero).TotalSeconds;
    var convertedDateTime2 = referenceTimeZero.AddSeconds(convertedUnixValue);

    //This works
    convertedDateTime2.Should()
                      .Be( convertedDateTime );

    //Test 2
    convertedDateTime = referenceTimeZero.AddSeconds(orignalUnixValue);
    var convertedUnixValueCasted = (Int64)(convertedDateTime - referenceTimeZero).TotalSeconds;
    convertedDateTime2 = referenceTimeZero.AddSeconds(convertedUnixValueCasted);

    //Difference of 1 second
    convertedDateTime2.Should()
                      .Be(convertedDateTime);

    //Test 3
    convertedDateTime = referenceTimeZero.AddSeconds(orignalUnixValue);
    convertedUnixValueCasted = (Int64)Math.Ceiling((convertedDateTime - referenceTimeZero).TotalSeconds);
    convertedDateTime2 = referenceTimeZero.AddSeconds(convertedUnixValueCasted);

    //Possible workaround - works for this example
    convertedDateTime2.Should()
                      .Be(convertedDateTime);
}

第一个测试显示转换值的正确方法,第二个显示当前实现,第三个包含我的解决方法。

【问题讨论】:

  • 听起来你的问题应该是“在 DateTime 和 Int64 之间转换而不会丢失信息”。 unix 时间戳部分听起来无关紧要......
  • 我们来谈谈您的业务需求。在您的时间戳/日期时间数据的生命周期内,它是否可以在任何时候包含重要且不应丢失的小数位/秒的分数?
  • @Jon Skeet 也许这是一个更好的标题。
  • @musium 这不是你可以失去的秒数。它是由 UNIX 时间戳的小数部分表示的秒的小数部分。
  • @musium:那应该是问题所在。您目前说过“这没问题,但我需要将转换后的值存储为 Int64”——这就是我的建议。目前还不清楚要求是什么(至少对我而言)——尤其是在您是否需要任何亚秒级值方面。

标签: c# datetime unix-timestamp


【解决方案1】:

编辑

如果您必须像使用 UNIX 时间戳一样使用这些值,那么我建议使用 Math.Round 而不是 Math.Ceiling。这样可以确保数字四舍五入到最接近的整数。

不过,正如 Jon 所建议的那样,使用 TimeSpan.Ticks 而不是 TimeSpan.TotalSeconds 更有意义,因为滴答声以 Int64 开头。这样就不会出现转换错误。


说明

您看到的问题似乎是由double 的存储方式以及将double 转换为long 时发生的情况引起的。

我稍微修改了你的代码:

const Int64 orignalUnixValue = 252000596321;
DateTime referenceTimeZero = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);

DateTime convertedDateTime = referenceTimeZero.AddSeconds(orignalUnixValue);
double convertedUnixValue = (convertedDateTime - referenceTimeZero).TotalSeconds;

Console.WriteLine(convertedUnixValue); // 252000596321
Console.WriteLine((long)convertedUnixValue); // 252000596320

如您所见,double 变量似乎 存储了正确的值。但是在转换为long 时会出现某种转换错误。

通过使用 Jon Skeet 的DoubleConverter,可以看到变量的值实际上小于252000596321,这是导致转换错误的原因。

Console.WriteLine(DoubleConverter.ToExactString(convertedUnixValue));
// 252000596320.999969482421875

【讨论】:

  • 好吧,这里也有算术运算——在TotalSeconds 部分中,你已经除以“每秒滴答数”了......这需要相当长的时间才能弄清楚到底发生了什么说实话。
  • 我必须更详细地查看它才能准确计算出来。您可以使用我的DoubleConverter 类来找出double精确 值 - 在pobox.com/~skeet/csharp/DoubleConverter.cs 下载它,然后使用DoubleConvert.ToExactString(d) 获得精确 d 的值作为字符串。
  • @JonSkeet 双精度值是252000596320.999969482421875。这解释了投射到long 时会发生什么。你认为在这里使用Math.Ceiling 合适吗?或者double 是否可以存储252000596321.0000000001 之类的内容,这会在四舍五入时导致类似的问题?
  • 正如我在评论中提到的,我将使用 Ticks 属性开始。这里真的没有必要使用浮点数。
  • 看来我需要使用 Math.Round。非常感谢您的帮助!
猜你喜欢
  • 1970-01-01
  • 2020-10-19
  • 2015-02-09
  • 1970-01-01
  • 2014-03-31
  • 2022-01-14
  • 2013-11-17
相关资源
最近更新 更多