SQL Server Round DateTime Up - sql-server

I need a function that rounds UP to the nearest x mins. I found the one below, but it rounds up or down to the nearest x mins. Based on another value, I would either need to round it UP to the nearest half hour or hour.
CREATE FUNCTION [dbo].[RoundTime] (#Time DATETIME, #RoundToMin INT)
RETURNS DATETIME
AS
BEGIN
RETURN ROUND(CAST(CAST(CONVERT(VARCHAR,#Time,121) AS DATETIME) AS FLOAT) * (1440/#RoundToMin),0)/(1440/#RoundToMin)
END
GO

This changes your function to round up to any number of minutes.
CREATE FUNCTION [dbo].[RoundUpTime] (#Time DATETIME, #RoundToMin INT)
RETURNS DATETIME
AS
BEGIN
return dateadd(mi, - datepart(mi, #time) + (datepart(mi, #time) + #roundToMin - 1) / #roundToMin * #roundToMin, #Time)
END
GO
Note I changed the name to RoundUpTime. Caveat, it works on the MINUTE section only, and ignores seconds and milliseconds.
select dbo.RoundUpTime('20121212 12:30:00.003', 30)
--
2012-12-12 12:30

Change ROUND to CEILING and take away the ", 0" from the ROUND:
CREATE FUNCTION [dbo].[RoundTime] (#Time DATETIME, #RoundToMin INT)
RETURNS DATETIME
AS
BEGIN
RETURN CEILING(CAST(CAST(CONVERT(VARCHAR,#Time,121) AS DATETIME) AS FLOAT) * (1440/#RoundToMin))/(1440/#RoundToMin)
END
GO

Related

T-SQL truncate time (not round) to the nearest minute

Looking for the most efficient and elegant way to do truncate the time to the minute
-- I need to truncate the time to the minute,
-- this code almost works but rounds up
SELECT
CAST('2021-09-02T15:15:30.9233333' AS datetime2(7)) AS EventDatetime2,
CAST(CAST('2021-09-02T15:15:30.9233333' AS datetime2(7)) AS TIME(0)) AS EventTime
As Larnu posted, if you want to round up or down depending on the seconds value, a simple convert to smalldatetime will do.
If you want to truncate, there are several ways, the simplest is probably just to add minutes to midnight (only posting because I prefer without the magic dates like 1900-01-01):
DECLARE #dt datetime2(7) = '2021-09-02T15:15:30.9233333';
DECLARE #d datetime2(7) = CONVERT(date, #dt);
SELECT DATEADD(MINUTE, DATEDIFF(MINUTE, #d, #dt), #d);
Another way is more intuitive but a little ugly:
DECLARE #dt datetime2(7) = '2021-09-02T15:15:30.9233333';
SELECT SMALLDATETIMEFROMPARTS
(
DATEPART(YEAR, #dt),
DATEPART(MONTH, #dt),
DATEPART(DAY, #dt),
DATEPART(HOUR, #dt),
DATEPART(MINUTE, #dt)
);
If you want to "round" to the nearest minute you could just CONVERT the value to a smalldatetime; they are only accurate to 1 minute:
SELECT CONVERT(smalldatetime,CONVERT(datetime2,'2021-09-02T15:15:30.9233333'));
If you want to, you can then CONVERT back to your original data type.
If you want to truncate (so strip the minutes) you could use the old DATEDIFF and DATEADD method:
DECLARE #DateTime2 datetime2(7) = '2021-09-02T15:15:30.9233333';
SELECT DATEADD(MINUTE,DATEDIFF(MINUTE,'19000101',#DateTime2),CONVERT(datetime2(7),'19000101'));
Just another option using left() and the implicit conversion.
Depending on the actual USE CASE, the outer convert() is optional
Example
DECLARE #dt datetime2(7) = '2021-09-02T15:15:30.9233333';
select convert(smalldatetime,left(#dt,16))
Results
2021-09-02 15:15:00
Combining the DATEADD and CAST(... AS SMALLDATETIME) approaches effectively gives you a minute "floor", like so:
SELECT CAST('2021-09-02T15:15:30.9233333' AS DATETIME2(7)) AS EventDatetime2,
CAST(CAST('2021-09-02T15:15:30.9233333' AS DATETIME2(7)) AS TIME(0)) AS EventTime,
CAST(CAST(DATEADD(
SECOND,
(DATEPART(SECOND, CAST('2021-09-02T15:15:30.9233333' AS DATETIME2(7))) * -1),
CAST('2021-09-02T15:15:30.9233333' AS DATETIME2(7))) AS SMALLDATETIME) AS TIME(0));
EventDatetime2
EventTime
(No column name)
2021-09-02 15:15:30.9233333
15:15:31
15:15:00
The DATEADD in this example subtracts the number of seconds from the datetime before converting it to a smalldatetime, so when that cast/convert does its rounding it will always go to the lower minute.
If, however, your input value is the sort of string literal that the wording of your question implies, you could also do this:
SELECT CAST(SUBSTRING('2021-09-02T15:15:30.9233333', CHARINDEX('T', '2021-09-02T15:15:30.9233333')+1, 6) + '00' AS TIME(0));
and get this result:
15:15:00

Calculating the Age from the Birthday function in SQL

I have made a function in SQL to calculate the Age from the Birthday and it is like this:
FUNCTION [dbo].[GetAge] (#birthday datetime, #date datetime)
RETURNS int
AS
BEGIN
return datediff(SECOND, #birthday, #date) / (365.23076923074 * 24 * 60 * 60)
END
The birthday is of format : 1963-01-01 00:00:00.000
My problem is that when I call the function like this :
SELECT dbo.GetAge(birthday, '2014-12-17 00:00:00')
FROM [dbo].[Users]
GO
it says:
Msg 535, Level 16, State 0, Line 3
The datediff function resulted in an overflow. The number of dateparts separating two date/time instances is too large. Try to use datediff with a less precise datepart.
BUT I call the same function with a date like :
SELECT dbo.GetAge(birthday, '1963-01-01 00:00:00')
FROM [dbo].[Users]
GO
I get the results..
So I don't understand what is the problem.
Pls help me and thank you in advance
The error says it all. "Try to use datediff with a less precise datepart"
return DATEDIFF(DAY, '1963-01-01 00:00:00', '2014-12-17 00:00:00') / (365.23076923074)
Seems obvious..the number of seconds from a user's birthday to today is too many for whatever datatype MySQL uses for DATEDIFF. But it's not too many from 1/1/1963.
Change your function to use a less precise datepart, i.e. minute instead of second.
FUNCTION [dbo].[GetAge] (#birthday datetime, #date datetime)
RETURNS int
AS
BEGIN
return datediff(MINUTE, #birthday, #date) / (365.23076923074 * 24 * 60)
END
OR hour
FUNCTION [dbo].[GetAge] (#birthday datetime, #date datetime)
RETURNS int
AS
BEGIN
return datediff(HOUR, #birthday, #date) / (365.23076923074 * 24)
END
OR DAY
FUNCTION [dbo].[GetAge] (#birthday datetime, #date datetime)
RETURNS int
AS
BEGIN
return datediff(DAY, #birthday, #date) / (365.23076923074)
END
Why not just DATEDIFF(year, #birthday, #date)? You only want the whole number of years, right?
Try casting your birthday to a date before sending it off.
SELECT dbo.GetAge(birthday, select cast('1963-01-01 00:00:00.000' as date))
FROM [dbo].[Users]
GO

convert decimal to time 6.80 =7.20 hrs in SQL [duplicate]

This question already has answers here:
Convert decimal time to hours and minutes
(4 answers)
Closed 9 years ago.
Please help me to do this
decimal 6.80 should equal to 7.20 hours
DECLARE #R1 decimal(4,2);
DECLARE #R2 decimal(4,2);
declare #Type1 decimal(4,2);
declare #Type2 decimal(4,2);
DECLARE #R1Time decimal(4,2);
DECLARE #R2Time decimal(4,2);
SET #R1=2.5
SET #R2=3.5
SET #Type1=17;
SET #Type2=7;
SET #R1Time=(FORMAT((ISNULL(60.0/NULLIF(#R1,0),0)),'N2'))
SET #R2Time=(FORMAT((ISNULL(60.0/NULLIF(#R2,0),0)),'N2'))
SELECT #R1Time as R1Min
SELECT #R2Time as R2Min
SELECT FORMAT(((#Type1*#R1Time)/60.0),'N2') R1Hrs -- 6.80 hours this = 24*17=408/60
SELECT FORMAT(((#Type2*#R2Time)/60.0),'N2') R2Hrs
SELECT CONVERT(CHAR(5), DATEADD(MINUTE, 60*(convert(decimal(4,2),FORMAT(((#Type1*#R1Time)/60.0),'N2'))), 0), 108);--6.48 hours
6.80 hours this = 24*17=408/60 this should be 7.20 hours not 6.48 is it?. Did I am wrong please help me thanks
#R1 is how many in an hour 60/2.5 =24min per Type1 and 17 Type1 is 17*24 =408 min then convert to time –
So what you are saying is that the number before the decimal seperator is correct (in hours) and the number after the decimal seperator is in the absolute amount of minutes?
Then in pseudocode you can do something like this:
INPUT = 6.80
HOURS = FLOOR(INPUT)
DECIMALS = (INPUT - HOURS) * 100
if ( DECIMALS > 60 )
{
HOURS = HOURS + 1
MINUTES = DECIMALS - 60
}
// Now hours and minutes are in the way you intended
Now all you have to do is convert this pseudocode into SQL code.
In function form, this would look something like this (I wrote this by heart so beware for typo's/ errors):
CREATE FUNCTION ChangeTime
-- Input current time decimal
(#CurrentTime decimal(2,1) )
RETURNS decimal(2,1) -- New time
AS
BEGIN
DECLARE #Hours int, #Minutes int;
SET #Hours = FLOOR(#CurrentTime);
SET #Minutes = (#CurrentTime - #Hours)*100;
IF #Minutes >= 60
BEGIN
SET #Hours = #Hours + 1; -- New hours
SET #Minutes = #Minutes - 60; -- New minutes
END
RETURN ( #Hours + (#Minutes/100) ) -- New (corrected) time
END
On the other hand, if you mean: I need to convert the decimals such that 6.50 becomes 6 hours and 30 minutes and 6.80 becomes 6 hours and 48 minutes, then we can change the function to the following:
CREATE FUNCTION ChangeTime
-- Input current time decimal
(#CurrentTime decimal(2,1) )
RETURNS decimal(2,1) -- New time
AS
BEGIN
DECLARE #Hours int, #Minutes int;
SET #Hours = FLOOR(#CurrentTime);
SET #Minutes = (#CurrentTime - #Hours)*100;
SET #Minutes = (#Minutes / 100) * 60; -- New minutes
RETURN ( #Hours + (#Minutes/100) ) -- New (corrected) time
END
If both of these functions do not get the desired result, then please update your question with an explanation of what you exactly want to achieve.
Good luck!
declare #hour decimal(6,2) = 6.8
select floor(#hour) + floor(#hour%1/.6) + #hour%1%.6
Result:
7.20
If you want it as a function:
CREATE FUNCTION f_convert(#hour decimal(6,2))
RETURNS decimal(6,2)
AS
BEGIN
RETURN floor(#hour) + floor(#hour%1/.6) + #hour%1%.6
END
Test(sqlserver 2008+):
SELECT dbo.f_convert(hour)
FROM (values (6.8),(3.9),(.59)) x(hour)
Result:
7.20
4.30
0.59

Convert UTC Milliseconds to DATETIME in SQL server

I want to convert UTC milliseconds to DateTime in SQL server.
This can easily be done in C# by following code:
DateTime startDate = new DateTime(1970, 1, 1).AddMilliseconds(1348203320000);
I need to do this in SQL server. I found some script here, but this was taking initial ticks from 1900-01-01.
I have used the DATEADD function as below, but this was giving an arithmetic overflow exception by supping milliseconds as difference:
SELECT DATEADD(MILLISECOND,1348203320000,'1970-1-1')
How can I do the conversion properly?
DECLARE #UTC BIGINT
SET #UTC = 1348203320997
SELECT DATEADD(MILLISECOND, #UTC % 1000, DATEADD(SECOND, #UTC / 1000, '19700101'))
Below the function that converts milliseconds to datetime
IF object_id('dbo.toDbTimeMSC', 'FN') IS NOT NULL DROP FUNCTION dbo.toDbTimeMSC
GO
CREATE FUNCTION [dbo].[toDbTimeMSC] (#unixTimeMSC BIGINT) RETURNS DATETIME
BEGIN
RETURN DATEADD(MILLISECOND, #unixTimeMSC % 1000, DATEADD(SECOND, #unixTimeMSC / 1000, '19700101'))
END
GO
-- select dbo.toDbTimeMSC(1348203320000)
I had problems with using answers given here (especially that the system was counting ticks form 0001-01-01) - so I did this:
CONVERT(DATETIME,[Time]/ 10000.0/1000/86400-693595)
--explanation for [Time_in_Ticks]/ 10000.0/1000/86400-693595
--Time is in "ticks"
--10000 = number of ticks in Milisecond
--1000 = number of milisecons in second
--86400 = number of seconds in a day (24hours*60minutes*60second)
--693595= number of days between 0001-01-01 and 1900-01-01 (which is base
-- date when converting from int to datetime)
Using SQL Server 2008R2 this produced the required result:
CAST(SWITCHOFFSET(CAST(dateadd(s, convert(bigint, [t_stamp]) / 1000, convert(datetime, '1-1-1970 00:00:00')) AS DATETIMEOFFSET), DATENAME (TZoffset, SYSDATETIMEOFFSET())) AS DATETIME)
The DATEADD requires an integer as a second argument. Your number 1348203320000 is very large for integer therefore it produce an error in runtime. Your should use bigint type instead and provide DATEADD with correct int values by splitting your milliseconds to seconds and milliseconds. That is sample you could use.
DECLARE #total bigint = 1348203320000;
DECLARE #seconds int = #total / 1000
DECLARE #milliseconds int = #total % 1000;
DECLARE #result datetime = '1970-1-1';
SET #result = DATEADD(SECOND, #seconds,#result);
SET #result = DATEADD(MILLISECOND, #milliseconds,#result);
SELECT #result
Right now, you can use dateadd with division on minutes and not seconds.
The code will be like this:
DATEADD(MILLISECOND, epoch% 60000, DATEADD(MINUTE, epoch/ 60000, '19700101'));
=dateadd("d",INT((Fields!lastLogon.Value / 864000000000)- 134774),"1970-01-01 00:00:00")
That's what I used in SSRS to get around the INT error, use days instead of seconds. Is it wrong?

what is the best way to store time interval in a SQL server database

i have a table where i want to track time so a valid entry might be:
1 hour 3 mins
47 mins
10 hours
3 mins 14 seconds
what field type is best used for this. i obviously could use varchar . .but i thought there might be something better as i would like to run queries to total the amount of time over a number of records.
Do not use character types to store date/time information.
In SQL Server 2008, you have the time type for this, which is what you should use if your time intervals are less than 1 day. In previous versions, or if you need to store larger intervals, you will have to use datetime or smalldatetime (depending on the precision you need).
Another option would be to choose a time unit - say, minutes - and just use an int value to represent the number of units. Just make sure that (a) the unit you choose is actually precise enough to record the smallest intervals you need to track, and (b) the actual numeric type is large enough to hold the largest intervals you need to track. A smallint might be sufficient for tracking the number of minutes within a day; on the other hand, tracking the number of milliseconds within a 10-year timeframe would have to be stored as a bigint.
Just use integer to store interval in seconds. DATEDIFF returns integer. Write a function that turns it into text. This one needs some adjustmens (so it shows "1 min", not "1 mins"), but should work ok:
CREATE FUNCTION dbo.SecondsToText(#seconds int)
RETURNS VARCHAR(100)
AS
BEGIN
declare #days int;
set #days = #seconds/(3600 * 24);
declare #hours int;
set #hours = (#seconds - #days * 3600 * 24) / 3600;
declare #minutes int;
set #minutes = (#seconds - #days * 3600 * 24 - #hours * 3600) / 60;
set #seconds = (#seconds - #days * 3600 * 24 - #hours * 3600 - #minutes * 60);
RETURN
RTRIM(CASE WHEN #days > 0 THEN CAST(#days as varchar) + ' days ' ELSE '' END +
CASE WHEN #hours > 0 THEN CAST(#hours as varchar) + ' hours ' ELSE '' END +
CASE WHEN #minutes > 0 THEN CAST(#minutes as varchar) + ' minutes ' ELSE '' END +
CASE WHEN #seconds > 0 THEN CAST(#seconds as varchar) + ' seconds ' ELSE '' END)
END
GO
As #Aaronaught said use a date/time or datetime (as necessary) data type to store your values; but these types only store an instance in time and not a time span or duration. You will need to use two fields to store an interval e.g. [time_span_start] and [time_span_end]. The difference between the two will give you the interval.
The longer answer to your question can be answered by downloading a copy of "Developing Time-Oriented Database Applications in SQL" by Richard T. Snodgrass. It's freely available as a PDF, have a look here:
http://www.cs.arizona.edu/~rts/publications.html
Depends on your range of time - either convert everything to seconds and just store that value as an INT, or if your span of times is larger, you might want to use fields for hours, minutes, seconds separately.
Also, SQL Server 2008 introduces a new TIME data type which allows you to store time-only values.
Related to Tony's answer, you can also use a single datetime column relative to a standard start time which is implicit for all intervals - for instance: 1/1/1900 12:00 AM.
In this case it is easy enough for storage:
INSERT INTO tbl (interval) VALUES (DATEADD(s, '1/1/1900', DATEDIFF(s, #starttime, #endtime))
Now this is not obviously easy for doing SUMs of rows, so you could think about adding persisted computed column(s) of DATEDIFF(s, '1/1/1900', interval) to provide seconds to perform SUMs.
Now, here's where it gets interesting for SQL Server:
Because of SQL Server's implementation for converting numbers to and from dates, 0 -> 1/1/1900 12:00 AM, 0.5 -> 1/1/1900 12:00 PM, 1 -> 1/2/1900 12:00 AM etc. i.e. the whole number is treated as the number of days since 1/1/1900 and the fractional part is the fraction within the day. So you CAN actually naively add these to get an interval.
And in fact:
SELECT CONVERT(DATETIME, 1) + CONVERT(DATETIME, 0) + CONVERT(DATETIME, 2) + CONVERT(DATETIME, 0.5)
gives '1900-01-04 12:00:00.000' as expected
So you can do this (going around SUM by converting):
DECLARE #datetest TABLE ( dt DATETIME NOT NULL )
INSERT INTO #datetest ( dt )
VALUES ( 0 )
INSERT INTO #datetest ( dt )
VALUES ( 1 )
INSERT INTO #datetest ( dt )
VALUES ( 2 )
INSERT INTO #datetest ( dt )
VALUES ( 0.5 )
SELECT *
FROM #datetest
SELECT CONVERT(DATETIME, SUM(CONVERT(FLOAT, dt)))
FROM #datetest
I'm not advocating doing this in general, YMMV, and any design solution you choose should be verified against all your requirements.

Resources