如何在 SQLServer 中将 bigint (UNIX 时间戳)转换为 datetime?

如何在 SQLServer 中将 UNIX 时间戳(bigint)转换为 DateTime?

412246 次浏览

像这样

将 Unix (epoch) datetime 以秒为单位添加到基准日期

这将得到它现在(2010-05-2507:56:23.000)

 SELECT dateadd(s,1274756183,'19700101 05:00:00:000')

如果你想倒车,看看这个 http://wiki.lessthandot.com/index.php/Epoch_Date

尝试:

CREATE FUNCTION dbo.fn_ConvertToDateTime (@Datetime BIGINT)
RETURNS DATETIME
AS
BEGIN
DECLARE @LocalTimeOffset BIGINT
,@AdjustedLocalDatetime BIGINT;
SET @LocalTimeOffset = DATEDIFF(second,GETDATE(),GETUTCDATE())
SET @AdjustedLocalDatetime = @Datetime - @LocalTimeOffset
RETURN (SELECT DATEADD(second,@AdjustedLocalDatetime, CAST('1970-01-01 00:00:00' AS datetime)))
END;
GO

这样就行了:

declare @UNIX_TIME int
select @UNIX_TIME = 1111111111
-- Using dateadd to add seconds to 1970-01-01
select [Datetime from UNIX Time] = dateadd(!precision!,@UNIX_TIME,'1970-01-01')

根据时间戳的精度,使用: ss,ms 或 mcs 代替精度。 比京特能够保持微秒级的精度。

这对我很有效:

Select
dateadd(S, [unixtime], '1970-01-01')
From [Table]

如果有人想知道为什么是1970-01-01,这就是 史诗时代

下面是来自维基百科的一段话:

自1970年1月1日(星期四)世界协调时00:00:00协调世界时(UTC)以来已经过去的秒数,[1][注1]不包括闰秒。

2038年问题

此外,DataAdd 函数接受一个 int作为要添加的秒数。因此,如果你试图增加超过 2147483647秒,你会得到一个算术溢出错误。为了解决这个问题,可以将添加分成两个调用 DateAdd,一个调用年份,另一个调用剩余的秒数。

Declare @t as bigint = 4147483645


Select (@t / @oneyear) -- Years to add
Select (@t % @oneyear) -- Remaining seconds to add


-- Get Date given a timestamp @t
Declare @oneyear as int = 31622400
Select DateAdd(SECOND, @t % @oneyear, DateAdd(YEAR, @t / @oneyear, '1970-01-01'))

这将允许您转换表示大于2038年的一年的时间戳。

这个函数将单位为毫秒的 unixtime 转换为日期时间。虽然丢失了毫秒,但仍然非常有用。

CREATE FUNCTION [dbo].[UnixTimestampToGMTDatetime]
(@UnixTimestamp bigint)
RETURNS datetime
AS
BEGIN
DECLARE @GMTDatetime datetime
select @GMTDatetime =
CASE
WHEN dateadd(ss, @UnixTimestamp/1000, '1970-01-01')
BETWEEN
Convert(DATETIME, Convert(VARCHAR(4), Year(dateadd(ss, @UnixTimestamp/1000, '1970-01-01') )) + '-03-' + Convert(VARCHAR(2), (31 - (5 * Year(dateadd(ss, @UnixTimestamp/1000, '1970-01-01') )/4 + 4) % 7)) + ' 01:00:00', 20)
AND
Convert(DATETIME, Convert(VARCHAR(4), Year(dateadd(ss, @UnixTimestamp/1000, '1970-01-01') )) + '-10-' + Convert(VARCHAR(2), (31 - (5 * Year(dateadd(ss, @UnixTimestamp/1000, '1970-01-01') )/4 + 1) % 7)) + ' 02:00:00', 20)
THEN Dateadd(hh, 1, dateadd(ss, @UnixTimestamp/1000, '1970-01-01'))
ELSE Dateadd(hh, 0, dateadd(ss, @UnixTimestamp/1000, '1970-01-01'))
END
RETURN @GMTDatetime
END

试试这个:

Sql 服务器:

SELECT dateadd(S, timestamp, '1970-01-01 00:00:00')
FROM
your_table

MySql 服务器:

SELECT
from_unixtime(timestamp)
FROM
your_table

Http://www.w3resource.com/mysql/date-and-time-functions/mysql-from_unixtime-function.php

如果有人出现以下错误:

将表达式转换为 int 数据类型时出现算术溢出错误

由于 unix 时间戳使用的是 bigint (而不是 int) ,因此可以使用以下代码:

SELECT DATEADD(S, CONVERT(int,LEFT(1462924862735870900, 10)), '1970-01-01')
FROM TABLE

将实际列的硬编码时间戳替换为 unix-time戳

资料来源: 以毫秒为单位的 MSSQL bigint Unix 时间戳到日期时间

这是基于 Daniel Little 为这个问题所做的工作,但是考虑到夏令时(适用于日期01-011902,由于 dateadd 函数的 int 限制更大) :

我们首先需要创建一个表来存储夏令时的日期范围(来源: 美国时间史) :

CREATE TABLE [dbo].[CFG_DAY_LIGHT_SAVINGS_TIME](
[BEGIN_DATE] [datetime] NULL,
[END_DATE] [datetime] NULL,
[YEAR_DATE] [smallint] NULL
) ON [PRIMARY]


GO


INSERT INTO CFG_DAY_LIGHT_SAVINGS_TIME VALUES
('2001-04-01 02:00:00.000',   '2001-10-27 01:59:59.997',    2001),
('2002-04-07 02:00:00.000',   '2002-10-26 01:59:59.997',    2002),
('2003-04-06 02:00:00.000',   '2003-10-25 01:59:59.997',    2003),
('2004-04-04 02:00:00.000',   '2004-10-30 01:59:59.997',    2004),
('2005-04-03 02:00:00.000',   '2005-10-29 01:59:59.997',    2005),
('2006-04-02 02:00:00.000',   '2006-10-28 01:59:59.997',    2006),
('2007-03-11 02:00:00.000',   '2007-11-03 01:59:59.997',    2007),
('2008-03-09 02:00:00.000',   '2008-11-01 01:59:59.997',    2008),
('2009-03-08 02:00:00.000',   '2009-10-31 01:59:59.997',    2009),
('2010-03-14 02:00:00.000',   '2010-11-06 01:59:59.997',    2010),
('2011-03-13 02:00:00.000',   '2011-11-05 01:59:59.997',    2011),
('2012-03-11 02:00:00.000',   '2012-11-03 01:59:59.997',    2012),
('2013-03-10 02:00:00.000',   '2013-11-02 01:59:59.997',    2013),
('2014-03-09 02:00:00.000',   '2014-11-01 01:59:59.997',    2014),
('2015-03-08 02:00:00.000',   '2015-10-31 01:59:59.997',    2015),
('2016-03-13 02:00:00.000',   '2016-11-05 01:59:59.997',    2016),
('2017-03-12 02:00:00.000',   '2017-11-04 01:59:59.997',    2017),
('2018-03-11 02:00:00.000',   '2018-11-03 01:59:59.997',    2018),
('2019-03-10 02:00:00.000',   '2019-11-02 01:59:59.997',    2019),
('2020-03-08 02:00:00.000',   '2020-10-31 01:59:59.997',    2020),
('2021-03-14 02:00:00.000',   '2021-11-06 01:59:59.997',    2021),
('2022-03-13 02:00:00.000',   '2022-11-05 01:59:59.997',    2022),
('2023-03-12 02:00:00.000',   '2023-11-04 01:59:59.997',    2023),
('2024-03-10 02:00:00.000',   '2024-11-02 01:59:59.997',    2024),
('2025-03-09 02:00:00.000',   '2025-11-01 01:59:59.997',    2025),
('1967-04-30 02:00:00.000',   '1967-10-29 01:59:59.997',    1967),
('1968-04-28 02:00:00.000',   '1968-10-27 01:59:59.997',    1968),
('1969-04-27 02:00:00.000',   '1969-10-26 01:59:59.997',    1969),
('1970-04-26 02:00:00.000',   '1970-10-25 01:59:59.997',    1970),
('1971-04-25 02:00:00.000',   '1971-10-31 01:59:59.997',    1971),
('1972-04-30 02:00:00.000',   '1972-10-29 01:59:59.997',    1972),
('1973-04-29 02:00:00.000',   '1973-10-28 01:59:59.997',    1973),
('1974-01-06 02:00:00.000',   '1974-10-27 01:59:59.997',    1974),
('1975-02-23 02:00:00.000',   '1975-10-26 01:59:59.997',    1975),
('1976-04-25 02:00:00.000',   '1976-10-31 01:59:59.997',    1976),
('1977-04-24 02:00:00.000',   '1977-10-31 01:59:59.997',    1977),
('1978-04-30 02:00:00.000',   '1978-10-29 01:59:59.997',    1978),
('1979-04-29 02:00:00.000',   '1979-10-28 01:59:59.997',    1979),
('1980-04-27 02:00:00.000',   '1980-10-26 01:59:59.997',    1980),
('1981-04-26 02:00:00.000',   '1981-10-25 01:59:59.997',    1981),
('1982-04-25 02:00:00.000',   '1982-10-25 01:59:59.997',    1982),
('1983-04-24 02:00:00.000',   '1983-10-30 01:59:59.997',    1983),
('1984-04-29 02:00:00.000',   '1984-10-28 01:59:59.997',    1984),
('1985-04-28 02:00:00.000',   '1985-10-27 01:59:59.997',    1985),
('1986-04-27 02:00:00.000',   '1986-10-26 01:59:59.997',    1986),
('1987-04-05 02:00:00.000',   '1987-10-25 01:59:59.997',    1987),
('1988-04-03 02:00:00.000',   '1988-10-30 01:59:59.997',    1988),
('1989-04-02 02:00:00.000',   '1989-10-29 01:59:59.997',    1989),
('1990-04-01 02:00:00.000',   '1990-10-28 01:59:59.997',    1990),
('1991-04-07 02:00:00.000',   '1991-10-27 01:59:59.997',    1991),
('1992-04-05 02:00:00.000',   '1992-10-25 01:59:59.997',    1992),
('1993-04-04 02:00:00.000',   '1993-10-31 01:59:59.997',    1993),
('1994-04-03 02:00:00.000',   '1994-10-30 01:59:59.997',    1994),
('1995-04-02 02:00:00.000',   '1995-10-29 01:59:59.997',    1995),
('1996-04-07 02:00:00.000',   '1996-10-27 01:59:59.997',    1996),
('1997-04-06 02:00:00.000',   '1997-10-26 01:59:59.997',    1997),
('1998-04-05 02:00:00.000',   '1998-10-25 01:59:59.997',    1998),
('1999-04-04 02:00:00.000',   '1999-10-31 01:59:59.997',    1999),
('2000-04-02 02:00:00.000',   '2000-10-29 01:59:59.997',    2000)
GO

现在我们为每个美国时区创建一个函数。这是假设 Unix 时间为毫秒。如果以秒为单位,则从代码中删除/1000:

太平洋

create function [dbo].[UnixTimeToPacific]
(@unixtime bigint)
returns datetime
as
begin
declare @pacificdatetime datetime
declare @interimdatetime datetime = dateadd(s, @unixtime/1000, '1970-01-01')
select  @pacificdatetime =  dateadd(hour,case when @interimdatetime between begin_date and end_date then -7 else -8 end  ,@interimdatetime)
from cfg_day_light_savings_time  where  year_date = datepart(year,@interimdatetime)
if @pacificdatetime is null
select @pacificdatetime= dateadd(hour, -7, @interimdatetime)
return @pacificdatetime
end

东区

create function [dbo].[UnixTimeToEastern]
(@unixtime bigint)
returns datetime
as
begin
declare @easterndatetime datetime
declare @interimdatetime datetime = dateadd(s, @unixtime/1000, '1970-01-01')
select  @easterndatetime =  dateadd(hour,case when @interimdatetime between begin_date and end_date then -4 else -5 end  ,@interimdatetime)
from cfg_day_light_savings_time  where  year_date = datepart(year,@interimdatetime)
if @easterndatetime is null
select @easterndatetime= dateadd(hour, -4, @interimdatetime)
return @easterndatetime
end

总部

create function [dbo].[UnixTimeToCentral]
(@unixtime bigint)
returns datetime
as
begin
declare @centraldatetime datetime
declare @interimdatetime datetime = dateadd(s, @unixtime/1000, '1970-01-01')
select  @centraldatetime =  dateadd(hour,case when @interimdatetime between begin_date and end_date then -5 else -6 end  ,@interimdatetime)
from cfg_day_light_savings_time  where  year_date = datepart(year,@interimdatetime)
if @centraldatetime is null
select @centraldatetime= dateadd(hour, -5, @interimdatetime)
return @centraldatetime
end

create function [dbo].[UnixTimeToMountain]
(@unixtime bigint)
returns datetime
as
begin
declare @mountaindatetime datetime
declare @interimdatetime datetime = dateadd(s, @unixtime/1000, '1970-01-01')
select  @mountaindatetime =  dateadd(hour,case when @interimdatetime between begin_date and end_date then -6 else -7 end  ,@interimdatetime)
from cfg_day_light_savings_time  where  year_date = datepart(year,@interimdatetime)
if @mountaindatetime is null
select @mountaindatetime= dateadd(hour, -6, @interimdatetime)
return @mountaindatetime
end

夏威夷

create function [dbo].[UnixTimeToHawaii]
(@unixtime bigint)
returns datetime
as
begin
declare @hawaiidatetime datetime
declare @interimdatetime datetime = dateadd(s, @unixtime/1000, '1970-01-01')
select  @hawaiidatetime =  dateadd(hour,-10,@interimdatetime)
from cfg_day_light_savings_time  where  year_date = datepart(year,@interimdatetime)


return @hawaiidatetime
end

亚利桑那州

create function [dbo].[UnixTimeToArizona]
(@unixtime bigint)
returns datetime
as
begin
declare @arizonadatetime datetime
declare @interimdatetime datetime = dateadd(s, @unixtime/1000, '1970-01-01')
select  @arizonadatetime =  dateadd(hour,-7,@interimdatetime)
from cfg_day_light_savings_time  where  year_date = datepart(year,@interimdatetime)


return @arizonadatetime
end

阿拉斯加

create function [dbo].[UnixTimeToAlaska]
(@unixtime bigint)
returns datetime
as
begin
declare @alaskadatetime datetime
declare @interimdatetime datetime = dateadd(s, @unixtime/1000, '1970-01-01')
select  @alaskadatetime =  dateadd(hour,case when @interimdatetime between begin_date and end_date then -8 else -9 end  ,@interimdatetime)
from cfg_day_light_savings_time  where  year_date = datepart(year,@interimdatetime)
if @alaskadatetime is null
select @alaskadatetime= dateadd(hour, -8, @interimdatetime)
return @alaskadatetime
end

N秒添加到 1970-01-01将得到一个 世界协调时日期,因为 Unix 时间戳 N自1970年1月1日(星期四)世界协调时00:00:00(世界协调世界时时)以来已经过去的秒数

在 SQLServer2016中,可以使用 AT TIME ZONE将一个时区转换为另一个时区。你只需要知道 Windows 标准格式的时区名称:

SELECT *
FROM (VALUES (1514808000), (1527854400)) AS Tests(UnixTimestamp)
CROSS APPLY (SELECT DATEADD(SECOND, UnixTimestamp, '1970-01-01') AT TIME ZONE 'UTC') AS CA1(UTCDate)
CROSS APPLY (SELECT UTCDate AT TIME ZONE 'Pacific Standard Time') AS CA2(LocalDate)
| UnixTimestamp | UTCDate                    | LocalDate                  |
|---------------|----------------------------|----------------------------|
| 1514808000    | 2018-01-01 12:00:00 +00:00 | 2018-01-01 04:00:00 -08:00 |
| 1527854400    | 2018-06-01 12:00:00 +00:00 | 2018-06-01 05:00:00 -07:00 |

或者简单地说:

SELECT *, DATEADD(SECOND, UnixTimestamp, '1970-01-01') AT TIME ZONE 'UTC' AT TIME ZONE 'Pacific Standard Time'
FROM (VALUES (1514808000), (1527854400)) AS Tests(UnixTimestamp)
| UnixTimestamp | LocalDate                  |
|---------------|----------------------------|
| 1514808000    | 2018-01-01 04:00:00 -08:00 |
| 1527854400    | 2018-06-01 05:00:00 -07:00 |

备注:

  • 您可以通过将 DATETIMEOFFSET转换为 DATETIME来截断时区信息。
  • 这个转换将夏时制考虑在内。太平洋时间是2018年1月的世界协调时08:00和2018年6月的世界协调时07:00。
//BIGINT UNIX TIMESTAMP CONVERSION upto Millisecond Accuracy
CREATE FUNCTION [dbo].[ConvertUnixTimestamp] (@Datetime [BIGINT]) RETURNS DATETIME
AS
BEGIN


RETURN DATEADD(MILLISECOND, cast(@Datetime as bigint) % 1000,
DATEADD(SECOND, (cast(@Datetime as bigint) / 1000)%60,
DATEADD(MINUTE, ((cast(@Datetime as bigint) / 1000)/60)%60,
DATEADD(HOUR, ((cast(@Datetime as bigint) / 1000)/60)/60, '19700101'))))
END

我也不得不面对这个问题。不幸的是,没有一个答案(在这里和其他几十个页面)是令我满意的,因为我仍然无法达到日期超过2038年,由于32位整数强制转换的地方。

最后一个对我有用的解决方案是使用 float变量,这样我至少可以有一个 2262-04-11T23:47:16.854775849的最大日期。尽管如此,这还不能覆盖整个 datetime域,但是对于我的需求已经足够了,并且可以帮助其他遇到同样问题的人。

-- date variables
declare @ts bigint; -- 64 bit time stamp, 100ns precision
declare @d datetime2(7) = GETUTCDATE(); -- 'now'
-- select @d = '2262-04-11T23:47:16.854775849'; -- this would be the max date


-- constants:
declare @epoch datetime2(7) = cast('1970-01-01T00:00:00' as datetime2(7));
declare @epochdiff int = 25567; -- = days between 1900-01-01 and 1970-01-01
declare @ticksofday bigint = 864000000000; -- = (24*60*60*1000*1000*10)


-- helper variables:
declare @datepart float;
declare @timepart float;
declare @restored datetime2(7);


-- algorithm:
select @ts = DATEDIFF_BIG(NANOSECOND, @epoch, @d) / 100; -- 'now' in ticks according to unix epoch
select @timepart = (@ts % @ticksofday) / @ticksofday; -- extract time part and scale it to fractional part (i. e. 1 hour is 1/24th of a day)
select @datepart = (@ts - @timepart) / @ticksofday; -- extract date part and scale it to fractional part
select @restored = cast(@epochdiff + @datepart + @timepart as datetime); -- rebuild parts to a datetime value


-- query original datetime, intermediate timestamp and restored datetime for comparison
select
@d original,
@ts unix64,
@restored restored
;


-- example result for max date:
-- +-----------------------------+-------------------+-----------------------------+
-- | original                    | unix64            | restored                    |
-- +-----------------------------+-------------------+-----------------------------+
-- | 2262-04-11 23:47:16.8547758 | 92233720368547758 | 2262-04-11 23:47:16.8533333 |
-- +-----------------------------+-------------------+-----------------------------+

有几点需要考虑:

  • 在我的例子中,100ns 精度是必需的,但是这似乎是64位 Unix 时间戳的标准分辨率。如果使用其他分辨率,则必须相应地调整 @ticksofday和算法的第一行。
  • 我使用的其他系统,有他们的问题与时区等,我发现最好的解决方案,我将始终使用 UTC。对于您的需求,这可能有所不同。
  • 1900-01-01datetime2的起始日期,就像 unix 时间戳的纪元 1970-01-01一样。
  • float帮助我解决了2038年问题和整数溢出等问题,但是请记住,浮点数的性能不是很好,可能会降低大量时间戳的处理速度。另外,由于舍入错误,浮点数可能会导致精度损失,正如您在上面最大日期的示例结果比较中所看到的(在这里,误差约为1.4425 ms)。
  • 在算法的最后一行中,对 datetime进行了强制转换。遗憾的是,不允许将数值显式转换为 datetime2,但允许将数值显式转换为 datetime,而这又被隐式转换为 datetime2。就目前而言,这可能是正确的,但在未来版本的 SQL Server 中可能会发生变化: 要么有一个 dateadd_big()函数,要么允许对 datetime2进行显式转换,要么不允许对 datetime进行显式转换,所以这可能会中断,或者有朝一日会有更简单的方法。

如果时间以毫秒为单位,并且需要保存它们:

DECLARE @value VARCHAR(32) = '1561487667713';


SELECT DATEADD(MILLISECOND, CAST(RIGHT(@value, 3) AS INT) - DATEDIFF(MILLISECOND,GETDATE(),GETUTCDATE()), DATEADD(SECOND, CAST(LEFT(@value, 10) AS INT), '1970-01-01T00:00:00'))

解决方案可以是:

DECLARE @UnixTimeStamp bigint = 1564646400000 /*2019-08-01 11:00 AM*/


DECLARE @LocalTimeOffset bigint = DATEDIFF(MILLISECOND, GETDATE(), GETUTCDATE());
DECLARE @AdjustedTimeStamp bigint = @UnixTimeStamp - @LocalTimeOffset;
SELECT [DateTime] = DATEADD(SECOND, @AdjustedTimeStamp % 1000, DATEADD(SECOND, @AdjustedTimeStamp / 1000, '19700101'));

对于 GMT 来说,这是最简单的方法:

Select dateadd(s, @UnixTime+DATEDIFF (S, GETUTCDATE(), GETDATE()), '1970-01-01')

@ DanielLittle 对这个问题有最简单最优雅的回答。然而,如果你有兴趣转换到一个特定的时区和考虑到 DST (夏令时) ,以下工作很好:

CAST(DATEADD(S, [UnixTimestamp], '1970-01-01') AT TIME ZONE 'UTC' AT TIME ZONE 'Pacific Standard Time' AS Datetime)

注意: 此解决方案仅适用于 SQLServer2016及以上版本(和 Azure)。

创建函数:

CREATE FUNCTION dbo.ConvertUnixTime (@input INT)
RETURNS Datetime
AS BEGIN
DECLARE @Unix Datetime


SET @Unix = CAST(DATEADD(S, @Input, '1970-01-01') AT TIME ZONE 'UTC' AT TIME ZONE 'Pacific Standard Time' AS Datetime)


RETURN @Unix
END

你可以这样调用函数:

SELECT   dbo.ConvertUnixTime([UnixTimestamp])
FROM     YourTable
CREATE FUNCTION dbo.ConvertUnixToDateTime(@Datetime BIGINT)
RETURNS DATETIME
AS
BEGIN
RETURN (SELECT DATEADD(second,@Datetime, CAST('1970-01-01' AS datetime)))
END;
GO

下面是我使用的函数,它通过执行两步 dateadd()来适用于大于2038年的日期。它返回 UTC,但有关时区和 DST 处理,请参见 肯尼的回答

IF OBJECT_ID('dbo.fn_ConvertUnixToDateTime') IS NULL
EXEC ('CREATE function dbo.fn_ConvertUnixToDateTime() returns int AS begin RETURN 0 end;')
GO
go
alter function dbo.fn_ConvertUnixToDateTime (@unixTimestamp BIGINT)
RETURNS DATETIME
AS
/*
Converts unix timestamp to utc datetime.


To work with larger timestamps it does a two-part add, since dateadd()
function only allows you to add int values, not bigint.
*/
BEGIN
RETURN (SELECT DATEADD( second
,   @unixTimestamp % 3600
,   dateadd(    hour
,   @unixTimestamp / 3600
,   CAST('1970-01-01 00:00:00' AS datetime)
)
)
)


END;

在 SQL 服务器上,您可以尝试这样做:

选择 Dateadd (S,CAST (CONVERT (INT,CONVERT (VARBINARY,SUBString (‘ Timestramp’,1,8) ,2)) AS NUMERIC (20)) ,‘1970-01-01’)

例如:

选择 Dateadd (S,CAST (CONVERT (INT,CONVERT (VARBINARY,SUBSTRING (’6369841c05df306d5dc81914’,1,8) ,2)) AS NUMERIC (20)) ,’1970-01-01’)

@ Salman@Daniel-little

我认为丹尼尔的答案是错误的。

他的逻辑只用闰年的几秒来计算,所以转换后的数值是不正确的。

我认为我们需要如下函数:

CREATE OR ALTER FUNCTION dbo.convert_unixtime_to_utc_datetime
(
@source_unix_time AS BIGINT
)
RETURNS DATETIME2
BEGIN
    

DECLARE @remaining_unix_time AS BIGINT = @source_unix_time
DECLARE @max_int_value AS BIGINT = 2147483647
DECLARE @unixtime_base_year AS INT = 1970
DECLARE @target_year AS INT = @unixtime_base_year
DECLARE @year_offset AS INT = 0
DECLARE @subtract_value AS BIGINT = 0
DECLARE @calc_base_datetime AS DATETIME2
DECLARE @seconds_per_day AS BIGINT = (60 /* seconds */ * 60 /* minutes */ * 24 /* hours */)
    

WHILE (1 = 1)
BEGIN
IF @remaining_unix_time <= @max_int_value
BREAK
        

IF ((@target_year % 400 = 0) OR ((@target_year % 4 = 0) AND (@target_year % 100 != 0)))
SET @subtract_value = (@seconds_per_day * 366 /* days */)
ELSE
SET @subtract_value = (@seconds_per_day * 365 /* days */)


SET @remaining_unix_time -= @subtract_value
SET @target_year += 1
SET @year_offset += 1
        

END
  

SET @calc_base_datetime = DATETIME2FROMPARTS(@unixtime_base_year + @year_offset, 1, 1, 0, 0, 0, 0, 0)


RETURN DATEADD(SECOND, @remaining_unix_time, @calc_base_datetime)
END
;

我使用以下组件验证了边缘情况:

  • DDL
CREATE TABLE test_convert_unixtime_table (
id BIGINT NOT NULL
, description nvarchar(max) NOT NULL
, source_utc_datetime datetime2 NOT NULL
, source_unixtime BIGINT NOT NULL
, CONSTRAINT pkc_test_convert_unixtime_table PRIMARY KEY (id)
) ;


ALTER TABLE test_convert_unixtime_table ADD CONSTRAINT idx_test_convert_unixtime_table_1
UNIQUE (source_unixtime) ;
  • DML
INSERT INTO test_convert_unixtime_table (id, description, source_utc_datetime, source_unixtime) VALUES (2, 'threshold of 2038 year problem', '2038/01/19 03:14:07', 2147483647);
INSERT INTO test_convert_unixtime_table (id, description, source_utc_datetime, source_unixtime) VALUES (3, 'threshold of 2038 year problem + 1 second', '2038/01/19 03:14:08', 2147483648);
INSERT INTO test_convert_unixtime_table (id, description, source_utc_datetime, source_unixtime) VALUES (4, 'leap year - 1 year before 2038
the first day of the year', '2035/01/01 00:00:00', 2051222400);
INSERT INTO test_convert_unixtime_table (id, description, source_utc_datetime, source_unixtime) VALUES (5, 'leap year - 1 year before 2038
the end of Feburary', '2035/02/28 23:59:59', 2056319999);
INSERT INTO test_convert_unixtime_table (id, description, source_utc_datetime, source_unixtime) VALUES (6, 'leap year - 1 year before 2038
the first day of March', '2035/03/01 00:00:00', 2056320000);
INSERT INTO test_convert_unixtime_table (id, description, source_utc_datetime, source_unixtime) VALUES (7, 'leap year - 1 year before 2038
new year's eve', '2035/12/31 23:59:59', 2082758399);
INSERT INTO test_convert_unixtime_table (id, description, source_utc_datetime, source_unixtime) VALUES (8, 'leap year before 2038
the first day of the year', '2036/01/01 00:00:00', 2082758400);
INSERT INTO test_convert_unixtime_table (id, description, source_utc_datetime, source_unixtime) VALUES (9, 'leap year before 2038
the end of Feburary', '2036/02/29 23:59:59', 2087942399);
INSERT INTO test_convert_unixtime_table (id, description, source_utc_datetime, source_unixtime) VALUES (10, 'leap year before 2038
the first day of March', '2036/03/01 00:00:00', 2087942400);
INSERT INTO test_convert_unixtime_table (id, description, source_utc_datetime, source_unixtime) VALUES (11, 'leap year before 2038
new year's eve', '2036/12/31 23:59:59', 2114380799);
INSERT INTO test_convert_unixtime_table (id, description, source_utc_datetime, source_unixtime) VALUES (12, 'leap year + 1 year before 2038
the first day of the year', '2037/01/01 00:00:00', 2114380800);
INSERT INTO test_convert_unixtime_table (id, description, source_utc_datetime, source_unixtime) VALUES (13, 'leap year + 1 year before 2038
the end of Feburary', '2037/02/28 23:59:59', 2119478399);
INSERT INTO test_convert_unixtime_table (id, description, source_utc_datetime, source_unixtime) VALUES (14, 'leap year + 1 year before 2038
the first day of March', '2037/03/01 00:00:00', 2119478400);
INSERT INTO test_convert_unixtime_table (id, description, source_utc_datetime, source_unixtime) VALUES (15, 'leap year + 1 year before 2038
new year's eve', '2037/12/31 23:59:59', 2145916799);
INSERT INTO test_convert_unixtime_table (id, description, source_utc_datetime, source_unixtime) VALUES (16, 'leap year - 1 year after 2038
the first day of the year', '2039/01/01 00:00:00', 2177452800);
INSERT INTO test_convert_unixtime_table (id, description, source_utc_datetime, source_unixtime) VALUES (17, 'leap year - 1 year after 2038
the end of Feburary', '2039/02/28 23:59:59', 2182550399);
INSERT INTO test_convert_unixtime_table (id, description, source_utc_datetime, source_unixtime) VALUES (18, 'leap year - 1 year after 2038
the first day of March', '2039/03/01 00:00:00', 2182550400);
INSERT INTO test_convert_unixtime_table (id, description, source_utc_datetime, source_unixtime) VALUES (19, 'leap year - 1 year after 2038
new year's eve', '2039/12/31 23:59:59', 2208988799);
INSERT INTO test_convert_unixtime_table (id, description, source_utc_datetime, source_unixtime) VALUES (20, 'leap year after 2038
the first day of the year', '2040/01/01 00:00:00', 2208988800);
INSERT INTO test_convert_unixtime_table (id, description, source_utc_datetime, source_unixtime) VALUES (21, 'leap year after 2038
the end of Feburary', '2040/02/29 23:59:59', 2214172799);
INSERT INTO test_convert_unixtime_table (id, description, source_utc_datetime, source_unixtime) VALUES (22, 'leap year after 2038
the first day of March', '2040/03/01 00:00:00', 2214172800);
INSERT INTO test_convert_unixtime_table (id, description, source_utc_datetime, source_unixtime) VALUES (23, 'leap year after 2038
new year's eve', '2040/12/31 23:59:59', 2240611199);
INSERT INTO test_convert_unixtime_table (id, description, source_utc_datetime, source_unixtime) VALUES (24, 'leap year + 1 year after 2038
the first day of the year', '2041/01/01 00:00:00', 2240611200);
INSERT INTO test_convert_unixtime_table (id, description, source_utc_datetime, source_unixtime) VALUES (25, 'leap year + 1 year after 2038
the end of Feburary', '2041/02/28 23:59:59', 2245708799);
INSERT INTO test_convert_unixtime_table (id, description, source_utc_datetime, source_unixtime) VALUES (26, 'leap year + 1 year after 2038
the first day of March', '2041/03/01 00:00:00', 2245708800);
INSERT INTO test_convert_unixtime_table (id, description, source_utc_datetime, source_unixtime) VALUES (27, 'leap year + 1 year after 2038
new year's eve', '2041/12/31 23:59:59', 2272147199);
  • 质疑
SELECT
*
, dbo.convert_unixtime_to_utc_datetime(source_unixtime) AS [by_myfunc]
, dbo.func_by_daniel_little(source_unixtime) AS [by_daniel_little]
FROM
test_convert_unixtime_table
ORDER BY
id;
  • 结果集
|id |description                                             |source_utc_datetime    |source_unixtime|by_myfunc              |by_daniel_little       |
|---|--------------------------------------------------------|-----------------------|---------------|-----------------------|-----------------------|
|1  |threshold of 2038 year problem - 1 second               |2038/01/19 03:14:06.000|2,147,483,646  |2038/01/19 03:14:06.000|2037/11/30 03:14:06.000|
|2  |threshold of 2038 year problem                          |2038/01/19 03:14:07.000|2,147,483,647  |2038/01/19 03:14:07.000|2037/11/30 03:14:07.000|
|3  |threshold of 2038 year problem + 1 second               |2038/01/19 03:14:08.000|2,147,483,648  |2038/01/19 03:14:08.000|2037/11/30 03:14:08.000|
|4  |leap year - 1 year before 2038 the first day of the year|2035/01/01 00:00:00.000|2,051,222,400  |2035/01/01 00:00:00.000|2034/11/14 00:00:00.000|
|5  |leap year - 1 year before 2038 the end of Feburary      |2035/02/28 23:59:59.000|2,056,319,999  |2035/02/28 23:59:59.000|2035/01/10 23:59:59.000|
|6  |leap year - 1 year before 2038 the first day of March   |2035/03/01 00:00:00.000|2,056,320,000  |2035/03/01 00:00:00.000|2035/01/11 00:00:00.000|
|7  |leap year - 1 year before 2038 new year's eve           |2035/12/31 23:59:59.000|2,082,758,399  |2035/12/31 23:59:59.000|2035/11/12 23:59:59.000|
|8  |leap year before 2038 the first day of the year         |2036/01/01 00:00:00.000|2,082,758,400  |2036/01/01 00:00:00.000|2035/11/13 00:00:00.000|
|9  |leap year before 2038 the end of Feburary               |2036/02/29 23:59:59.000|2,087,942,399  |2036/02/29 23:59:59.000|2036/01/10 23:59:59.000|
|10 |leap year before 2038 the first day of March            |2036/03/01 00:00:00.000|2,087,942,400  |2036/03/01 00:00:00.000|2036/01/11 00:00:00.000|
|11 |leap year before 2038 new year's eve                    |2036/12/31 23:59:59.000|2,114,380,799  |2036/12/31 23:59:59.000|2036/11/11 23:59:59.000|
|12 |leap year + 1 year before 2038 the first day of the year|2037/01/01 00:00:00.000|2,114,380,800  |2037/01/01 00:00:00.000|2036/11/12 00:00:00.000|
|13 |leap year + 1 year before 2038 the end of Feburary      |2037/02/28 23:59:59.000|2,119,478,399  |2037/02/28 23:59:59.000|2037/01/09 23:59:59.000|
|14 |leap year + 1 year before 2038 the first day of March   |2037/03/01 00:00:00.000|2,119,478,400  |2037/03/01 00:00:00.000|2037/01/10 00:00:00.000|
|15 |leap year + 1 year before 2038 new year's eve           |2037/12/31 23:59:59.000|2,145,916,799  |2037/12/31 23:59:59.000|2037/11/11 23:59:59.000|
|16 |leap year - 1 year after 2038 the first day of the year |2039/01/01 00:00:00.000|2,177,452,800  |2039/01/01 00:00:00.000|2038/11/11 00:00:00.000|
|17 |leap year - 1 year after 2038 the end of Feburary       |2039/02/28 23:59:59.000|2,182,550,399  |2039/02/28 23:59:59.000|2039/01/07 23:59:59.000|
|18 |leap year - 1 year after 2038 the first day of March    |2039/03/01 00:00:00.000|2,182,550,400  |2039/03/01 00:00:00.000|2039/01/08 00:00:00.000|
|19 |leap year - 1 year after 2038 new year's eve            |2039/12/31 23:59:59.000|2,208,988,799  |2039/12/31 23:59:59.000|2039/11/09 23:59:59.000|
|20 |leap year after 2038 the first day of the year          |2040/01/01 00:00:00.000|2,208,988,800  |2040/01/01 00:00:00.000|2039/11/10 00:00:00.000|
|21 |leap year after 2038 the end of Feburary                |2040/02/29 23:59:59.000|2,214,172,799  |2040/02/29 23:59:59.000|2040/01/07 23:59:59.000|
|22 |leap year after 2038 the first day of March             |2040/03/01 00:00:00.000|2,214,172,800  |2040/03/01 00:00:00.000|2040/01/08 00:00:00.000|
|23 |leap year after 2038 new year's eve                     |2040/12/31 23:59:59.000|2,240,611,199  |2040/12/31 23:59:59.000|2040/11/08 23:59:59.000|
|24 |leap year + 1 year after 2038 the first day of the year |2041/01/01 00:00:00.000|2,240,611,200  |2041/01/01 00:00:00.000|2040/11/09 00:00:00.000|
|25 |leap year + 1 year after 2038 the end of Feburary       |2041/02/28 23:59:59.000|2,245,708,799  |2041/02/28 23:59:59.000|2041/01/06 23:59:59.000|
|26 |leap year + 1 year after 2038 the first day of March    |2041/03/01 00:00:00.000|2,245,708,800  |2041/03/01 00:00:00.000|2041/01/07 00:00:00.000|
|27 |leap year + 1 year after 2038 new year's eve            |2041/12/31 23:59:59.000|2,272,147,199  |2041/12/31 23:59:59.000|2041/11/08 23:59:59.000|

我的函数返回的值都匹配源日期时间,但是,丹尼尔的逻辑返回的值并不都匹配源日期时间。