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')
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'))
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
在 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)
SELECT *, DATEADD(SECOND, UnixTimestamp, '1970-01-01') AT TIME ZONE 'UTC' AT TIME ZONE 'Pacific Standard Time'
FROM (VALUES (1514808000), (1527854400)) AS Tests(UnixTimestamp)
-- 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 |
-- +-----------------------------+-------------------+-----------------------------+
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
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;
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|