函数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图。对于正值,直线是直线,并且随着时间增加。但是,对于负值,该图随着整数值的左间断而逐段减小。发生了什么事?
为什么这样的图?这是错误吗?如果通过设计,为什么?在1890年代,时间并没有倒退。 ToOADate函数违反了许多合理的假设
DateComponent函数可以简化为纯算术吗?
答案 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特殊情况下的负值实现。
// 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;
}