【问题标题】:.NET DateTime, different resolution when converting to and from OADate?.NET DateTime,在转换为 OADate 和从 OADate 转换时分辨率不同?
【发布时间】:2010-09-16 06:57:24
【问题描述】:

我正在将 DateTime 转换为 OADate。在将 OADate 转换回来时,我希望得到完全相同的 DateTime,但现在它只有毫秒分辨率,因此有所不同。

var a = DateTime.UtcNow;
double oadate = a.ToOADate();
var b = DateTime.FromOADate(oadate);
int compare = DateTime.Compare(a, b); 

//Compare is not 0; the date times are not the same

报价来自:634202170964319073

来自 b 的报价:634202170964310000

OADate 双精度:40437.290467951389

这是什么原因? DateTime 的分辨率显然已经够用了。

【问题讨论】:

    标签: c# .net datetime resolution ole-automation


    【解决方案1】:

    我认为这是一个很好的问题。 (我才发现的。)

    除非您使用非常接近 1900 年的日期进行操作,否则 DateTime 将具有比 OA 日期更高的精度。但是由于一些模糊的原因,DateTime 结构的作者只是 loveDateTime 和其他东西之间进行转换时将其截断到最接近的整毫秒。不用说,这样做会毫无理由地丢掉很多精确度。

    这里有一个解决方法:

    static readonly DateTime oaEpoch = new DateTime(1899, 12, 30);
    
    public static DateTime FromOADatePrecise(double d)
    {
      if (!(d >= 0))
        throw new ArgumentOutOfRangeException(); // NaN or negative d not supported
    
      return oaEpoch + TimeSpan.FromTicks(Convert.ToInt64(d * TimeSpan.TicksPerDay));
    }
    
    public static double ToOADatePrecise(this DateTime dt)
    {
      if (dt < oaEpoch)
        throw new ArgumentOutOfRangeException();
    
      return Convert.ToDouble((dt - oaEpoch).Ticks) / TimeSpan.TicksPerDay;
    }
    

    现在,让我们考虑(根据您的问题)由以下人员给出的DateTime

    var ourDT = new DateTime(634202170964319073);
    // .ToSting("O") gives 2010-09-16T06:58:16.4319073
    

    任何DateTime 的精度为 0.1 µs。

    在我们正在考虑的日期和时间附近,OA 日期的精度为:

    Math.Pow(2.0, -37.0) 天,或大约 0.6286 µs

    我们得出结论,在这个地区DateTime 比 OA 日期精确(刚好超过)六倍。

    让我们使用我上面的扩展方法将ourDT 转换为double

    double ourOADate = ourDT.ToOADatePrecise();
    // .ToString("G") gives 40437.2904679619
    // .ToString("R") gives 40437.290467961888
    

    现在,如果您使用上面的静态FromOADatePrecise 方法将ourOADate 转换回DateTime,您会得到

    2010-09-16T06:58:16.4319072(写成"O"格式)

    与原始版本相比,我们看到在这种情况下精度损失为 0.1 µs。我们预计精度损失在 ±0.4 µs 以内,因为此间隔的长度为 0.8 µs,与前面提到的 0.6286 µs 相当。

    如果我们采用另一种方式,从 double 开始,表示不太接近 1900 年的 OA 日期,首先使用FromOADatePrecise,然后然后 ToOADatePrecise,然后我们回到double,因为中间DateTime 的精度优于OA 日期,我们期望在这种情况下完美往返。另一方面,如果您以相同的顺序使用 BCL 方法 FromOADateToOADate,则极不可能获得良好的往返(除非我们开始使用的 double 具有非常特殊的形式) .

    【讨论】:

    • 而且以前更糟。几年前我发现了一个错误,有时日期会被四舍五入到最接近的毫秒错误,它可能会导致长达两天的错误。幸运的是,我认为这最终得到了解决。
    • 嗨@Jeppe - 感谢您出色的写作。 OA 日期的计算 Math.Pow(2.0, -37.0) days, or circa 0.6286 µs 是如何得出的?我无法理解这个:-)
    • @Martin Ba,OLE 自动化日期实现为 IEEE 双精度(64 位)二进制浮点数,其值是从 1899 年 12 月 30 日午夜开始的天数。浮点数表示小数部分可用的位数取决于数字的整数部分有多大。如上所示,我们示例中的整数部分是 40437。只要它在 32768 和 65535 之间,小数部分将剩下 37 位,并且我提到的精度成立。
    【解决方案2】:

    ToOADate 调用的静态方法清楚地将刻度除以 10000,然后将结果存储在 long 中,从而删除任何亚毫秒信息

    有人知道在哪里可以找到 OADate 格式的规范吗?

        private static double TicksToOADate(long value)
        {
            if (value == 0L)
            {
                return 0.0;
            }
            if (value < 0xc92a69c000L)
            {
                value += 0x85103c0cb83c000L;
            }
            if (value < 0x6efdddaec64000L)
            {
                throw new OverflowException(Environment.GetResourceString("Arg_OleAutDateInvalid"));
            }
            long num = (value - 0x85103c0cb83c000L) / 0x2710L;
            if (num < 0L)
            {
                long num2 = num % 0x5265c00L;
                if (num2 != 0L)
                {
                    num -= (0x5265c00L + num2) * 2L;
                }
            }
            return (((double)num) / 86400000.0);
        }
    

    【讨论】:

    【解决方案3】:

    可能与 double 的精度有关,而不是 DateTime。

    【讨论】:

    • 转换后的双精度为 40437.290467951389,看起来很精确,但您可能是正确的。
    • 是的,双精度已经足够精确了,但是由于双精度的存储方式,日期时间并不能精确地表示为双精度。
    • @Ezombort 它看起来很精确,只是因为秒和天之间的转换因子。如果您将您提到的double 乘以24.0 * 60.0 * 60.0,您将只看到三位小数。这种大小的“任意”double 将显示五位小数(如果使用.ToString("R") 显示,则为七位小数)。所以没有使用double 的完整精度。请参阅我的新答案。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-12-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-08-03
    • 2017-04-19
    相关资源
    最近更新 更多