使用bigint的舍入错误在SQL中记录到datetime

时间:2016-04-08 14:08:11

标签: .net sql-server datetime

在我们的.NET前端,我们使用ticks来处理时间本地化,在数据库中我们以UTC格式存储所有值。今天我们在将记录存储到数据库时遇到奇怪的舍入错误,我们使用以下公式将Ticks转换为日期时间。

CAST((@ticks - 599266080000000000) / 10000000 / 24 / 60 / 60 AS datetime)

在我们发现以下舍入错误之前,它似乎对我们尝试的大多数时间值都正常工作:

DECLARE @ticks bigint = 635953248000000000; -- 2016-04-04​ 00:00:00.000
SELECT CAST((@Ticks - 599266080000000000) / 10000000 / 24 / 60 / 60 AS datetime)
-- Results in 2016-04-03 23:59:59.997

问题是:导致此舍入错误的原因是什么以及修复它的最佳做法是什么?

3 个答案:

答案 0 :(得分:1)

如果您在sql_variant中捕获计算,则可以确定它的类型。在您的情况下,计算使用的数字类型不是精确的数据类型,这是舍入发生的地方:

DECLARE @myVar sql_variant = (@Ticks - 599266080000000000) / 10000000 / 24 / 60 / 60 

SELECT SQL_VARIANT_PROPERTY(@myVar,'BaseType') BaseType,
SQL_VARIANT_PROPERTY(@myVar,'Precision') Precisions,
SQL_VARIANT_PROPERTY(@myVar,'Scale') Scale,
SQL_VARIANT_PROPERTY(@myVar,'TotalBytes') TotalBytes,
SQL_VARIANT_PROPERTY(@myVar,'Collation') Collation,
SQL_VARIANT_PROPERTY(@myVar,'MaxLength') MaxLengths

产生以下输出:

BaseType    Precisions  Scale   TotalBytes  Collation   MaxLengths
numeric     38          18      17          NULL        13

我发现此代码适用于Extended.Net link

DECLARE @ticks bigint = 635953248000000000

-- First, we will convert the ticks into a datetime value with UTC time 
 DECLARE @BaseDate datetime; 
 SET @BaseDate = '01/01/1900'; 

 DECLARE @NetFxTicksFromBaseDate bigint; 
 SET @NetFxTicksFromBaseDate = @Ticks - 599266080000000000; 
-- The numeric constant is the number of .Net Ticks between the System.DateTime.MinValue (01/01/0001) and the SQL Server datetime base date (01/01/1900) 

 DECLARE @DaysFromBaseDate int; 
 SET @DaysFromBaseDate = @NetFxTicksFromBaseDate / 864000000000; 
-- The numeric constant is the number of .Net Ticks in a single day. 

 DECLARE @TimeOfDayInTicks bigint; 
 SET @TimeOfDayInTicks = @NetFxTicksFromBaseDate - @DaysFromBaseDate * 864000000000; 

 DECLARE @TimeOfDayInMilliseconds int; 
 SET @TimeOfDayInMilliseconds = @TimeOfDayInTicks / 10000; 
-- A Tick equals to 100 nanoseconds which is 0.0001 milliseconds 

 DECLARE @UtcDate datetime; 
 SET @UtcDate = DATEADD(ms, @TimeOfDayInMilliseconds, DATEADD(d, @DaysFromBaseDate, @BaseDate)); 
-- The @UtcDate is already useful. If you need the time in UTC, just return this value. 

 SELECT @UtcDate;

答案 1 :(得分:0)

我找到了以下作品:

DECLARE @ticks bigint = 635953248000000000; -- 2016-04-04​ 00:00:00.000

SELECT  DATEADD(s,(@ticks %CONVERT(BIGINT,864000000000))/CONVERT(BIGINT,10000000),DATEADD(d,@ticks /CONVERT(BIGINT,864000000000)-CONVERT(BIGINT,639905),CONVERT(DATETIME,'1/1/1753')))
-- Results in 2016-04-04​ 00:00:00.000

Source

我无法告诉你舍入错误的来源。

答案 2 :(得分:0)

问题出现是因为datetime没有将值存储到毫秒的精度:

  

日期时间值四舍五入为.000,.003或.007秒的增量

Source

你的值是42462.000000000000000000,如果我在C#中转换为DateTime,则会出现在2016-04-03 23:59:59.995中,这将由SQL舍入为.997。