I need to convert a SQL Server DATETIME value to FILETIME in a T-SQL SELECT statement (on SQL Server 2000). Is there a built-in function to do this? If not, can someone help me figure out how to implement this conversion routine as a UDF (or just plain Transact-SQL)? Here is what I know:
FILETIME is 64-bit value representing the number of 100-nanosecond intervals since
January 1, 1601 (UTC) (per MSDN: FILETIME Structure).
SQL Server base time starts on 1900-01-01 00:00:00 (per SELECT CAST(0 as DATETIME)).
I found several examples showing how to convert FILETIME values to T-SQL DATETIME (I'm not 100% sure they are accurate, though), but could not find anything about reverse conversion. Even the general idea (or algorithm) would help.
Okay, I think I was able to implement this myself. Here is the function:
IF EXISTS
(
SELECT 1
FROM sysobjects
WHERE id = OBJECT_ID('[dbo].[fnDateTimeToFileTime]')
AND type = 'FN'
)
BEGIN
DROP FUNCTION [dbo].[fnDateTimeToFileTime]
END
GO
-- Create function.
CREATE FUNCTION [dbo].[fnDateTimeToFileTime]
(
#DateTime AS DATETIME
)
RETURNS
BIGINT
BEGIN
IF #DateTime IS NULL
RETURN NULL
DECLARE #MsecBetween1601And1970 BIGINT
DECLARE #MsecBetween1970AndDate BIGINT
SET #MsecBetween1601And1970 = 11644473600000
SET #MsecBetween1970AndDate =
DATEDIFF(ss, CAST('1970-01-01 00:00:00' as DATETIME), #DateTime) *
CAST(1000 AS BIGINT)
RETURN (#MsecBetween1601And1970 + #MsecBetween1970AndDate) * CAST(10000 AS BIGINT)
END
GO
IF ##ERROR = 0
GRANT EXECUTE ON [dbo].[fnDateTimeToFileTime] TO Public
GO
It seems to be accurate up to 1 second, which is okay with me (I could not make it more accurate due to data overflow). I used the TimeAndDate web tool to calculate the durations between dates.
What do you think?
2 SQL Server time era starts on
1900-01-01 00:00:00 (per SELECT CAST(0
as DATETIME).
No, that is the base date, datetime starts at 1753
run this
select cast('17800122' as datetime)
output
1780-01-22 00:00:00.000
But this is still less than filetime so you need to add that...however remember the gregorian and Julian calendars (also the reason that datetime starts at 1753)
The accepted answer work well, but will crash for date above 19 January 2038. Either use
DATEDIFF_BIG instead of DATEDIFF if you are on SQL Server 2016 or above, or use the following correction
CREATE FUNCTION [dbo].[fnDateTimeToFileTime]
(
#DateTime AS DATETIME
)
RETURNS
BIGINT
BEGIN
IF #DateTime IS NULL
RETURN NULL
DECLARE #MsecBetween1601And1970 BIGINT
DECLARE #MsecBetween1970AndDate BIGINT
DECLARE #MaxNumberDayBeforeOverflowDateDiff int;
SET #MaxNumberDayBeforeOverflowDateDiff = 24855; --SELECT DATEDIFF(day, CAST('1970-01-01 00:00:00' as DATETIME), CAST('2038-01-19 00:00:00' as DATETIME))
DECLARE #nbMaxDaysBetween1970AndDate int;
SET #nbMaxDaysBetween1970AndDate = DATEDIFF(day, CAST('1970-01-01 00:00:00' as DATETIME), #DateTime) / #MaxNumberDayBeforeOverflowDateDiff;
DECLARE #moduloResteDay int
SET #moduloResteDay = DATEDIFF(day, CAST('1970-01-01 00:00:00' as DATETIME), #DateTime) % #MaxNumberDayBeforeOverflowDateDiff;
DECLARE #nbSecondBefore19700101And20380119 bigint = 2147472000;
SET #MsecBetween1601And1970 = 11644473600000;
DECLARE #DateTimeModulo datetime;
SET #DateTimeModulo = DATEADD(day, -#nbMaxDaysBetween1970AndDate * #MaxNumberDayBeforeOverflowDateDiff, #DateTime)
SET #MsecBetween1970AndDate = CAST(CAST(#nbMaxDaysBetween1970AndDate as bigint) * #nbSecondBefore19700101And20380119 +
DATEDIFF(ss, CAST('1970-01-01 00:00:00' as DATETIME), #DateTimeModulo) as bigint)*
CAST(1000 AS BIGINT)
RETURN (#MsecBetween1601And1970 + #MsecBetween1970AndDate) * CAST(10000 AS BIGINT)
END
Related
What would be the Postgres equivalent for TO_UTC_TIMESTAMP_TZ function in oracle.
For the below query:
SELECT TO_UTC_TIMESTAMP_TZ('1998-01-01') FROM DUAL;
The result is "01-JAN-98 12.00.00.000000000 AM GMT" in oracle.
I am not entirely sure what the Oracle function does, but I think this should be equivalent:
TO_TIMESTAMP('1998-01-01', 'yyyy-mm-dd') at time zone 'UTC' at time zone 'GMT'
Append 'T00:00 UTC' and cast to timestamptz.
select ('1998-01-01'||'T00:00 UTC')::timestamptz;
Check:
select to_char(
('1998-01-01'||'T00:00 UTC')::timestamptz,
'yyyy-mm-dd hh24:mi:ss.us OF'
);
Result "1998-01-01 02:00:00.000000 +02".
This is exactly the same as "01-JAN-98 12.00.00.000000000 AM GMT".
It might make sense to define a compatibility function.
create function to_utc_timestamp_tz(ts text) returns timestamptz immutable as
$$
select (ts || 'T00:00 UTC')::timestamptz;
$$ language sql;
I am trying to mimic this calculation from Excel into T-SQL.
The first value is "7/25/2021 6:44:00 AM"
The second value is "7/25/2021 1:10:00 PM"
I am trying to come up with the value of 0.35138..
In SQL table, both values are currently in nvarchar(max) data type.
I am trying to write SQL Function, and I am stuck with process of Convert or Cast and do the calculation of two values.
So far, this is what I have:
CREATE FUNCTION [dbo].[fn_break_cal] (
#Punch_Start nvarchar(max),
#Punch_End nvarchar(max)
)
RETURNS nvarchar(max) AS
BEGIN
DECLARE #return_value nvarchar(max);
SET #return_value =
CONVERT(varchar, #Punch_Start, 103)
RETURN #return_value
First, I am not sure how to do subtraction after I convert into date or datetime.
Whenver I use Datediff, output is Integer, and I am not sure how to make it to numeric like (0.35133..).
What is best approach of dealing with data from nvarchar(max) --> calculation of date/datetime?
Thanks in advance.
Please refer to the below added function and the function call. I am not sure why you return nvarchar(max) value in the function. I changed that to Decimal. I used CAST to cast the nvarchar to datetime. you can use TRY_CAST as well.
ALTER FUNCTION [dbo].[fn_break_cal] (
#Punch_Start nvarchar(max),
#Punch_End nvarchar(max)
)
RETURNS decimal(36, 8) AS
BEGIN
DECLARE #return_value nvarchar(max);
SET #return_value = DATEDIFF(MINUTE, CAST(#Punch_Start AS datetime2) , CAST(#Punch_End AS datetime2) ) / (24* 60.0)
RETURN #return_value
END
Function call with params and return value:
Select [dbo].[fn_break_cal] ('7/25/2021 6:44:00 AM','7/25/2021 15:10:00 PM') return_value
DECLARE #ps nvarchar(30) = '7/25/2021 6:44:00 AM'
DECLARE #pe nvarchar(30) = '7/25/2021 15:10:00 PM'
SELECT
DATEDIFF(MINUTE, TRY_CONVERT(datetime, #ps), TRY_CONVERT(datetime, #pe)) / (24 * 60.0) AS fraction
SELECT
LEFT(CONVERT(time, TRY_CONVERT(datetime, #pe) - TRY_CONVERT(datetime, #ps)), 5) AS ' hr : mm '
-- more than 24 hrs
DECLARE #ps nvarchar(30) = '7/25/2021 6:44:00 AM'
DECLARE #pe nvarchar(30) = '7/27/2021 15:10:00 PM'
SELECT
CONCAT((DATEDIFF(MINUTE, TRY_CONVERT(datetime, #ps), TRY_CONVERT(datetime, #pe)) / 60),
':',
(DATEDIFF(MINUTE, TRY_CONVERT(datetime, #ps), TRY_CONVERT(datetime, #pe)) % 60)) ' hr : mm '
I can't figure out how to call a function with inputs specified from another table.
Let us assume the following function is being used to create a time interval:
create or replace function interval_generator(dt_start timestamp with TIME ZONE,
dt_end timestamp with TIME ZONE,
round_interval INTERVAL)
returns TABLE(time_start timestamp with TIME ZONE,
time_end timestamp with TIME ZONE) as $$
BEGIN
return query
SELECT
(n) time_start,
(n + round_interval) time_end
FROM generate_series(date_trunc('minute', dt_start), dt_end, round_interval) n;
END
$$
LANGUAGE 'plpgsql';
Let us create a dummy table for the minimal example:
DROP TABLE IF EXISTS lookup;
CREATE TEMP TABLE lookup
as
select *
from (
VALUES
('2017-08-17 04:00:00.000'::timestamp),
('2017-08-17 05:00:00.000'::timestamp),
('2017-08-18 06:00:00.000'::timestamp)
) as t (datetime);
Now my attempt is as follows:
select interval_generator(
SELECT datetime FROM lookup Order By datetime limit 1,
SELECT datetime FROM lookup Order By datetime Desc limit 1,
'1 hours'::interval
);
and it just yields the generic error ERROR: syntax error at or near "SELECT"
Enclose the SELECT statements in parentheses to make them expressions like this:
select * from interval_generator(
(SELECT datetime FROM lookup Order By datetime limit 1),
(SELECT datetime FROM lookup Order By datetime Desc limit 1),
'1 hours'::interval
);
Please note that
SELECT datetime FROM lookup Order By datetime limit 1
is exactly
SELECT min(datetime) FROM lookup
which seems to me better readable. As the function body of interval_generator comprises of a single SQL query why don't you make it a plain SQL function instead of pl/pgsql?
<your-function-declaration> as $$
SELECT
(n) time_start,
(n + round_interval) time_end
FROM generate_series(date_trunc('minute', dt_start), dt_end, round_interval) n;
$$
LANGUAGE 'sql';
I was updating a stored procedure to change the reporting time to clients timezone. The stored procedure is comprised of a dynamic SQL, which contains the time #timeoffset parameter of smallint data type.
DECLARE #sql VARCHAR(MAX) =
N'SELECT DISTINCT cl.ClientId,
CASE WHEN DATEADD(HOUR,'+CONVERT(CHAR(2),#timeoffset)+', x.changedate) >= '''+ CONVERT(varchar, #start_date) +'''
AND DATEADD(HOUR,'+CONVERT(CHAR(2),#timeoffset)+', x.changedate) < '''+ CONVERT(varchar, #end_date_plus1) +'''
THEN DATEADD(HOUR,'+CONVERT(CHAR(2),#timeoffset)+',x.changedate)'
To change the time to the clients' time zone, I need to subtract the #timeoffset. Making it negative doesn't change the output.
Trying to add the (-) before the conversion, would raises an error as subtraction operator is invalid for varchar. Writing it
without the conversion raises an error
'Conversion failed when converting the nvarchar value to data type smallint.
Can anyone assist me with this please? Thanks.
You could try passing the negative number as another variable:
DECLARE #neg INT = -1;
DECLARE #sql VARCHAR(MAX) =
N'SELECT DISTINCT cl.ClientId,
CASE WHEN DATEADD(HOUR,'+CONVERT(CHAR(2),#timeoffset)*#neg+', x.changedate) >= '''+
CONVERT(varchar, #start_date) +'''
AND DATEADD(HOUR,'+CONVERT(CHAR(2),#timeoffset)*#neg+', x.changedate) < '''+
CONVERT(varchar, #end_date_plus1) +'''
THEN DATEADD(HOUR,'+CONVERT(CHAR(2),#timeoffset)*#neg+',x.changedate)'
I've noticed a few problems in the code you've posted:
You are declaring #Sql as varchar(max), but you are using the N prefix before setting it's value. You only need that when dealing with unicode data (nchar ,nvarcar). This is critical but you should be aware of this.
You are using convert without specifying the style parameter. It's not that bad when converting ints to strings, but it can cause unexpected behaviors when dealing with dates. Whenever you need to use string representation for date/datetime values, you should always use the ISO8601 standard, since it's guaranteed that Sql server will always convert it to date correctly, regardless of local settings. To convert a datetime value to ISO8061 standard, use style 126 in the convert statement.
You are using varchar without specifying the length. This is a bad habit since SQL Server have different default values to the length depending on context. It's 1 when declaring a variable, but 30 when used in cast and convert.
I've done some changes to your code, including changing the char(2) you've used to varchar(11) (because 11 chars will contain even the minimum value of the int data type which is -2147483648) for the #timeoffset parameter, and had no problems with it being negative.
Here is my test:
DECLARE #timeOffset int = -10,
#start_date datetime = getdate(),
#sql nvarchar(max)
SET #sql =
N'SELECT '''+ convert(char(23), #start_date, 126) +''' As GetDate,
DATEADD(HOUR, '+ CAST(#timeOffset as varchar(11)) +', '''+ convert(char(23), #start_date, 126) +''') As DateAdd';
SELECT #Sql
EXEC(#sql)
Results:
SELECT '2018-11-08T20:33:31.670' As GetDate,
DATEADD(HOUR, -10, '2018-11-08T20:33:31.670') As DateAdd
GetDate DateAdd
2018-11-08T20:33:31.670 08.11.2018 10:33:3
I want to create a function to get the right week number of year.
I already posted here to find a 'native' solution, but apparently there is not.
I tryed to create funcrtion based on this mysql example
Here is the code translated to postgresql:
CREATE OR REPLACE FUNCTION week_num_year(_date date)
RETURNS integer AS
$BODY$declare
_year integer;
begin
select date_part('year',_date) into _year;
return ceil((to_char(_date,'DDD')::integer+(to_char(('01-01-'||_year)::date,'D')::integer%7-7))/7);
end;$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
But it gives wrong result, can someone help me ?
My config: PostgreSQL 9.2
If you want proper week numbers use:
select extract(week from '2012-01-01'::date);
This will produce the result 52, which is correct if you look on a calendar.
Now, if you actually want to define week numbers as "Every 7 days starting with the first day of the year" that's fine, though it doesn't match the week numbers anyone else uses and has some odd quirks:
select floor((extract(doy from '2011-01-01'::date)-1)/7)+1;
By the way, parsing date strings and hacking them up with string functions is almost always a really bad idea.
create or replace function week_num_year(_date date)
returns integer as
$body$
declare
_year date;
_week_number integer;
begin
select date_trunc('year', _date)::date into _year
;
with first_friday as (
select extract(doy from a::date) ff
from generate_series(_year, _year + 6, '1 day') s(a)
where extract(dow from a) = 5
)
select floor(
(extract(doy from _date) - (select ff from first_friday) - 1) / 7
) + 2 into _week_number
;
return _week_number
;
end;
$body$
language plpgsql immutable
You can retrieve the day of the week and also the week of the year by running:
select id,extract(DOW from test_date),extract(week from test_date), testdate,name from yourtable
What about the inbuild extract function?
SELECT extract (week from current_timestamp) FROM A_TABLE_FROM_YOUR_DB;