【问题标题】:Convert SQL Server DateTime object to BIGINT (.Net ticks)将 SQL Server DateTime 对象转换为 BIGINT(.Net 刻度)
【发布时间】:2011-11-15 05:50:41
【问题描述】:

我需要将 DateTime 类型值转换为 .Net 刻度格式的 BIGINT 类型(自 0001 年 1 月 1 日午夜 12:00:00 以来经过的 100 纳秒间隔数)。

应在 Sql server 2008 中使用 T-SQL 查询执行转换

例如:

DateTime value - 12/09/2011 00:00:00

将转换为:

BIGINT value - 634513824000000000

【问题讨论】:

  • 您要在哪里执行转换?您不能将值作为 .NET DateTime 获取,然后只使用 Ticks 属性吗?
  • @Jon Skeet 很抱歉缺少信息。我正在尝试使用 T-SQL 查询在 SQL Server 中执行转换
  • 您必须更新原始问题
  • +1,在试图找出答案的过程中,我学到了一些东西,所以谢谢你的提问:-)

标签: .net sql sql-server


【解决方案1】:

我争论过是否要发布这个,因为它取决于日期在 SQL Server 中的二进制级别是如何存储的,所以这是一个非常脆弱的解决方案。除了一次性转换之外,我会使用类似于@Solution Evangelist 发布的答案。不过,你可能会觉得这在学术上很有趣,所以我还是会发布它。

利用DateTime2 的准确性与.NET 中的滴答持续时间相匹配并且两者都基于01-01-0001 00:00:00.0000000 的开始日期这一事实,您可以将DateTime 转换为DateTime2,并且然后将其转换为binary(9):0x07F06C999F3CB7340B

日期时间信息是 RTL 存储的,所以反过来,我们会得到0x0B34B73C9F996CF007

前三个字节存储自01-01-0001 以来的天数,接下来的 5 个字节存储自当天午夜以来的 100ns 滴答声,因此我们可以取天数乘以一天中的滴答声并加上表示当天经过的时间的刻度。

执行以下代码:

set @date = getdate()
set @ticksPerDay = 864000000000

declare @date2 datetime2 = @date

declare @dateBinary binary(9) = cast(reverse(cast(@date2 as binary(9))) as binary(9))
declare @days bigint = cast(substring(@dateBinary, 1, 3) as bigint)
declare @time bigint = cast(substring(@dateBinary, 4, 5) as bigint)

select @date as [DateTime], @date2 as [DateTime2], @days * @ticksPerDay + @time as [Ticks]

返回以下结果:

DateTime                DateTime2              Ticks
----------------------- ---------------------- --------------------
2011-09-12 07:20:32.587 2011-09-12 07:20:32.58 634514088325870000

获取返回的 Ticks 数量并在 .NET 中转换回 DateTime:

DateTime dt = new DateTime(634514088325870000);
dt.ToString("yyyy-MM-dd HH:mm:ss.fffffff").Dump();

从 sql server 获取我们返回的日期:

2011-09-12 07:20:32.5870000

【讨论】:

  • 发送您的详细帖子。对于一次性转换,它是一个很好的解决方案,但不是我的情况。
【解决方案2】:

我找到了一篇可能有帮助的 CodeProject 文章:Convert DateTime To .NET Ticks Using T-SQL

我附上了上面文章中的SQL函数(我希望这可以吗?因为它需要注册。)

CREATE FUNCTION [dbo].[MonthToDays365] (@month int)
RETURNS int
WITH SCHEMABINDING
AS
-- converts the given month (0-12) to the corresponding number of days into the year (by end of month)
-- this function is for non-leap years
BEGIN 
RETURN
    CASE @month
        WHEN 0 THEN 0
        WHEN 1 THEN 31
        WHEN 2 THEN 59
        WHEN 3 THEN 90
        WHEN 4 THEN 120
        WHEN 5 THEN 151
        WHEN 6 THEN 181
        WHEN 7 THEN 212
        WHEN 8 THEN 243
        WHEN 9 THEN 273
        WHEN 10 THEN 304
        WHEN 11 THEN 334
        WHEN 12 THEN 365
        ELSE 0
    END
END

GO

CREATE FUNCTION [dbo].[MonthToDays366] (@month int)
RETURNS int 
WITH SCHEMABINDING
AS
-- converts the given month (0-12) to the corresponding number of days into the year (by end of month)
-- this function is for leap years
BEGIN 
RETURN
    CASE @month
        WHEN 0 THEN 0
        WHEN 1 THEN 31
        WHEN 2 THEN 60
        WHEN 3 THEN 91
        WHEN 4 THEN 121
        WHEN 5 THEN 152
        WHEN 6 THEN 182
        WHEN 7 THEN 213
        WHEN 8 THEN 244
        WHEN 9 THEN 274
        WHEN 10 THEN 305
        WHEN 11 THEN 335
        WHEN 12 THEN 366
        ELSE 0
    END
END

GO

CREATE FUNCTION [dbo].[MonthToDays] (@year int, @month int)
RETURNS int
WITH SCHEMABINDING
AS
-- converts the given month (0-12) to the corresponding number of days into the year (by end of month)
-- this function is for non-leap years
BEGIN 
RETURN 
    -- determine whether the given year is a leap year
    CASE 
        WHEN (@year % 4 = 0) and ((@year % 100  != 0) or ((@year % 100 = 0) and (@year % 400 = 0))) THEN dbo.MonthToDays366(@month)
        ELSE dbo.MonthToDays365(@month)
    END
END

GO

CREATE FUNCTION [dbo].[TimeToTicks] (@hour int, @minute int, @second int)  
RETURNS bigint 
WITH SCHEMABINDING
AS 
-- converts the given hour/minute/second to the corresponding ticks
BEGIN 
RETURN (((@hour * 3600) + CONVERT(bigint, @minute) * 60) + CONVERT(bigint, @second)) * 10000000
END

GO

CREATE FUNCTION [dbo].[DateToTicks] (@year int, @month int, @day int)
RETURNS bigint
WITH SCHEMABINDING
AS
-- converts the given year/month/day to the corresponding ticks
BEGIN 
RETURN CONVERT(bigint, (((((((@year - 1) * 365) + ((@year - 1) / 4)) - ((@year - 1) / 100)) + ((@year - 1) / 400)) + dbo.MonthToDays(@year, @month - 1)) + @day) - 1) * 864000000000;
END

GO

CREATE FUNCTION [dbo].[DateTimeToTicks] (@d datetime)
RETURNS bigint
WITH SCHEMABINDING
AS
-- converts the given datetime to .NET-compatible ticks
-- see https://msdn.microsoft.com/en-us/library/system.datetime.ticks(v=vs.110).aspx
BEGIN 
RETURN 
    dbo.DateToTicks(DATEPART(yyyy, @d), DATEPART(mm, @d), DATEPART(dd, @d)) +
    dbo.TimeToTicks(DATEPART(hh, @d), DATEPART(mi, @d), DATEPART(ss, @d)) +
    (CONVERT(bigint, DATEPART(ms, @d)) * CONVERT(bigint,10000));
END

GO

【讨论】:

  • 我相信只需要 DateToTicks 功能,您就可以删除其他人
  • 我已经逐字发布了 Eron Wright 的代码,但它不是我的,并且经受住了时间的考验(自 2003 年以来!)。我还注意到他将使用 DateTimeToTicks 函数,这取决于其他函数。
【解决方案3】:

我错过了这个问题的毫秒级单行解决方案,所以这里有一个:

SELECT ROUND(CAST(CAST(GETUTCDATE() AS FLOAT)*8.64e8 AS BIGINT),-1)*1000+599266080000000000

8.64e8 = TimeSpan.TicksPerDay / 1000
599266080000000000 = DateTime.Parse('1900-01-01').Ticks

这适用于 DATETIME 类型,但不适用于 DATETIME2。 DATETIME 的 4/3 毫秒分辨率使得有必要涉及 ROUND(...,-1):乘以 8.64e8 后,浮点结果总是以 0 或 33.3 或 66.6 结尾。这四舍五入为 0、30 或 70。

【讨论】:

  • 所以我看到DATETIME2 崩溃是因为Explicit conversion from data type datetime2 to float is not allowed, 但承认我不明白为什么,确切地说。 gbn does, however, provide a solution 再加一个CAST,这次是DATETIMESELECT ROUND(CAST(CAST(CAST(SYSUTCDATETIME() as DATETIME) AS FLOAT)*8.64e8 AS BIGINT),-1)*1000+599266080000000000
  • 对于计算“(TimeSpan = DateTimeB-DateTimeA).Ticks”并将其转换为 BIGINT 作为我的 1 次数据迁移也很有效。谢谢。
【解决方案4】:

您可以使用下面的 sql 将日期或 utcdate 转换为刻度

declare @date datetime2 = GETUTCDATE() or getdate()
declare @dateBinary binary(9) = cast(reverse(cast(@date as binary(9))) as binary(9))
declare @days bigint = cast(substring(@dateBinary, 1, 3) as bigint)
declare @time bigint = cast(substring(@dateBinary, 4, 5) as bigint)

select @date as [DateTime],  @days * 864000000000 + @time as [Ticks]

并使用下面的 sql 将刻度转换为日期

SELECT Converted = CAST(635324318540000000/864000000000.0 - 693595.0 AS DATETIME)

【讨论】:

    【解决方案5】:

    啧啧,

    这是简化的 #1 建议,但我不敢相信这是最佳结果。当然这是内置的,但在这一点上,无论我在截止日期前。重构花费了不到 1 分钟的时间,但也许它会帮助其他正在寻找一站式解决方案的人。

    CREATE FUNCTION [dbo].[Ticks] (@dt DATETIME)
    RETURNS BIGINT
    WITH SCHEMABINDING
    AS
    BEGIN 
    DECLARE @year INT = DATEPART(yyyy, @dt)
    DECLARE @month INT = DATEPART(mm, @dt)
    DECLARE @day INT = DATEPART(dd, @dt)
    DECLARE @hour INT = DATEPART(hh, @dt)
    DECLARE @min INT = DATEPART(mi, @dt)
    DECLARE @sec INT = DATEPART(ss, @dt)
    
    DECLARE @days INT =
        CASE @month - 1
            WHEN 0 THEN 0
            WHEN 1 THEN 31
            WHEN 2 THEN 59
            WHEN 3 THEN 90
            WHEN 4 THEN 120
            WHEN 5 THEN 151
            WHEN 6 THEN 181
            WHEN 7 THEN 212
            WHEN 8 THEN 243
            WHEN 9 THEN 273
            WHEN 10 THEN 304
            WHEN 11 THEN 334
            WHEN 12 THEN 365
        END
        IF  @year % 4 = 0 AND (@year % 100  != 0 OR (@year % 100 = 0 AND @year % 400 = 0)) AND @month > 2 BEGIN
            SET @days = @days + 1
        END
    RETURN CONVERT(bigint, 
        ((((((((@year - 1) * 365) + ((@year - 1) / 4)) - ((@year - 1) / 100)) + ((@year - 1) / 400)) + @days) + @day) - 1) * 864000000000) +
        ((((@hour * 3600) + CONVERT(bigint, @min) * 60) + CONVERT(bigint, @sec)) * 10000000) + (CONVERT(bigint, DATEPART(ms, @dt)) * CONVERT(bigint,10000));
    
    END
    GO
    

    【讨论】:

    • 至少有人关心重构 :) +1
    【解决方案6】:

    编辑:由于更新了有关原始问题的详细信息而进行了更新

    要提供 100ns 的精度,您应该在 SQL Server 2008 上使用新的日期类型 - datetime2 执行算法,精度为 100 纳秒。显然链接到已经在其他帖子中提供的算法本身。

    DateTime.Ticks Property

    一个滴答表示一百纳秒或一千万分之一 一秒钟。一毫秒内有 10,000 个滴答声。

    这个属性的值代表100-纳秒的次数 自 0001 年 1 月 1 日午夜 12:00:00 以来经过的时间间隔, 它代表 DateTime.MinValue。它不包括数量 可归因于闰秒的滴答声。

    DateTime dateTime = DateTime.Now;
    System.Int64 ticks = dateTime.Ticks;
    

    【讨论】:

      【解决方案7】:

      更高效的解决方案可能是:(注意前两行仅用于测试)

      DECLARE @YourDate DATETIME
      
      SET @YourDate = GETDATE()
      
      SELECT 
      (
        (
        -- seconds since 1970
        CAST(DATEDIFF(s,'1970-01-01 12:00:00',@YourDate) As BIGINT)
        )
      -- seconds from 0001 to 1970 (approximate)
      + 62125920000
      ) 
      -- make those seconds nanoseconds
      * 1000000000
      

      鉴于您的输入日期只下降到秒,我们只需以秒计算,然后乘以 1000000000 即可得到纳秒。

      【讨论】:

        【解决方案8】:

        这个函数返回unix时间:

        alter FUNCTION [dbo].[GETTICKS] (@datetime datetime)
        RETURNS bigint
        WITH SCHEMABINDING
        AS
        BEGIN 
            RETURN 
                DATEPART(millisecond,@datetime) +
                CONVERT(bigint, 
                            1000 * (
                                DATEPART(second,@datetime) +
                                60 * DATEPART(minute,@datetime) +
                                3600 * DATEPART(hour,@datetime) +
                                3600 * 24 * DATEPART(DAYOFYEAR,@datetime) +
                                convert(bigint, 3600 * 24 * (((DATEPART(year,@datetime) - 1970) * 365) + ((DATEPART(year,@datetime) - 1972) / 4)))
                        ) )
        END
        

        【讨论】:

          【解决方案9】:
          CREATE FUNCTION UtcToTicks( @d datetime) RETURNS bigint
             AS
             BEGIN
                 declare @d1 datetime
                 set @d1 = convert(datetime,0)
                 declare @days bigint = DATEDIFF(d,@d1,@d)
                 declare @time bigint = DATEDIFF(ms,convert(datetime,@days),@d)
                 return 599266080000000000 + 864000000000 * @days + @time * 10000
             END
          

          【讨论】:

          • 您的答案可以通过额外的支持信息得到改进。请edit 添加更多详细信息,例如引用或文档,以便其他人可以确认您的答案是正确的。你可以找到更多关于如何写好答案的信息in the help center
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2016-11-29
          • 2020-03-04
          • 1970-01-01
          相关资源
          最近更新 更多