时间在1890年代倒退了吗?如何解释负值的DateTime.ToOADate的令人惊讶的图表?

时间:2018-07-05 10:53:24

标签: .net datetime

函数DateTime.ToOADate将DateTime值映射到数字。

下面的功能可以简化吗?

// drop time component
double DateComponent(double date) => DateTime.FromOADate(date).Date.ToOADate();

它不等于Math.Floor吗?我说。但是,当我彻底测试(使用FsCheck)时,发现负值会给出不同的结果:

 > DateComponent(-4.1)
 -4
 > Math.Floor(-4.1)
 -5

DateComponent似乎四舍五入而不是向下舍入。

为了更好地理解,我绘制了一个DateTime.ToOADate图。对于正值,直线是直线,并且随着时间增加。但是,对于负值,该图随着整数值的左间断而逐段减小。发生了什么事?

  1. 为什么这样的图?这是错误吗?如果通过设计,为什么?在1890年代,时间并没有倒退。 ToOADate函数违反了许多合理的假设

      f随着时间增加,即。 t1
    • f是连续的
  2. DateComponent函数可以简化为纯算术吗?

graph of DateTime.ToOADate

2 个答案:

答案 0 :(得分:3)

OLE automation date的定义是“有趣”:

  

OLE自动化日期被实现为浮点数,其整数部分是1899年12月30日午夜之前或之后的天数,其小数部分代表该天的时间除以24。例如,午夜,1899年12月31日以1.0表示; 1900年1月1日上午6点以2.25表示; 1899年12月29日午夜以-1.0表示;和1899年12月29日上午6点以-1.25表示。

所以这不是0和OLE日期之间的简单距离。实际上,它是一个“编码”的日期(整数部分)和一个时间(小数部分)。这产生了一个有趣的悖论:

DateTime.FromOADate(0.1) == DateTime.FromOADate(-0.1)

在两种情况下,整数部分均为0,小数部分为0.1:-)(在两种情况下均为 1899-12-30 02:24:00

现在...您只需使用Math.Truncate或将其强制转换为long即可截断值。

static double DateComponent(double date) => Math.Truncate(date);

static double DateComponent(double date) => (long)date;

更具权威性的page解释了OLE日期的格式,其中以0.75 / -0.75给出了示例。

答案 1 :(得分:0)

阅读.NET参考源,ToOADate和FromOADate特殊情况下的负值实现。

https://github.com/Microsoft/referencesource/blob/3b1eaf5203992df69de44c783a3eda37d3d4cd10/mscorlib/system/datetime.cs#L548

// Converts an OLE Date to a tick count.
// This function is duplicated in COMDateTime.cpp
internal static long DoubleDateToTicks(double value) {
    // The check done this way will take care of NaN
    if (!(value < OADateMaxAsDouble) || !(value > OADateMinAsDouble))
        throw new ArgumentException(Environment.GetResourceString("Arg_OleAutDateInvalid"));

    // Conversion to long will not cause an overflow here, as at this point the "value" is in between OADateMinAsDouble and OADateMaxAsDouble
    long millis = (long)(value * MillisPerDay + (value >= 0? 0.5: -0.5));
    // The interesting thing here is when you have a value like 12.5 it all positive 12 days and 12 hours from 01/01/1899
    // However if you a value of -12.25 it is minus 12 days but still positive 6 hours, almost as though you meant -11.75 all negative
    // This line below fixes up the millis in the negative case
    if (millis < 0) {
        millis -= (millis % MillisPerDay) * 2;
    }

    millis += DoubleDateOffset / TicksPerMillisecond;

    if (millis < 0 || millis >= MaxMillis) throw new ArgumentException(Environment.GetResourceString("Arg_OleAutDateScale"));
    return millis * TicksPerMillisecond;
}