SQL Server - Round TIME values to the next minute - sql

I've found many posts about rounding "down" time values (e.g. https://stackoverflow.com/a/6667041/468823), but I have another problem: I wanna round to the higher minute and not to the lower, how can I do?
My code:
SELECT
PA.ORE AS TOT_HOURS,
CAST(CAST(PA.ORA_INIZIO AS DATETIME) AS TIME) AS BEGIN_TIME,
CAST(dateadd(minute, datediff(minute, 0, (CAST(PA.ORA_INIZIO AS DATETIME))), 0) AS TIME) AS BEGIN_TIME_ROUNDED
FROM PRG_ATTIVITA PA INNER JOIN PRG_TIPI_ATTIVITA PTA ON PA.ID_TIPO_ATTIVITA = PTA.ID_TIPO_ATTIVITA
INNER JOIN PER_ANAGRAFICA PAN ON PA.ID_DIPENDENTE = PAN.ID_DIPENDENTE
WHERE PA.ID_PROGETTO = 1431 and pta.DESCR_TIPO_ATTIVITA like 'F-%remoto%' and ID_ATTIVITA = 41772
ORDER BY PA.DATA_ATTIVITA
My result is the following:
TOT_HOURS BEGIN_TIME BEGIN_TIME_ROUNDED
1.50 15:59:59.9970000 15:59:00.0000000
I want BEGIN_TIME_ROUNDED = 16:00:00.0000000
NOTES:
1. I must convert my data { CAST(PA.ORA_INIZIO AS DATETIME) } because in the database I have time data as float values
2. BEGIN_TIME is the real value of my time value after conversion

SELECT DATEADD(MINUTE, CEILING(DATEDIFF(SECOND, 0, CAST(CAST(PA.ORA_INIZIO AS DATETIME) AS TIME)) / 60.0), DATEDIFF(DAY, 0, PA.ORA_INIZIO)) AS BEGIN_TIME_ROUNDED
EDIT
As pointed out in a comment this fails for times between 0 and 1 second. This can be combatted by simply changing the precision in the ceiling from seconds to milliseconds:
SELECT PA.ORA_INIZIO,
DATEADD(MINUTE,
CEILING(DATEDIFF(MILLISECOND, 0, CAST(PA.ORA_INIZIO AS TIME)) / 60000.0),
DATEDIFF(DAY, 0, PA.ORA_INIZIO)) AS BEGIN_TIME_ROUNDED
FROM (VALUES
(CONVERT(DATETIME, '20211126 15:59:00.997')),
(CONVERT(DATETIME, '20211126 15:59:00.004'))
) AS PA (ORA_INIZIO);
Which gives:
ORA_INIZIO
BEGIN_TIME_ROUNDED
2021-11-26 15:59:59.997
2021-11-26 16:00:00.000
2021-11-26 15:59:00.003
2021-11-26 16:00:00.000

Just CAST to smalldatetime for rounding to nearest minute
SELECT
CAST(CAST('15:59:59.9970000' AS time) AS smalldatetime),
CAST(CAST('15:59:30.0030000' AS time) AS smalldatetime),
CAST(CAST('15:59:30.0000000' AS time) AS smalldatetime),
CAST(CAST('15:59:29.9970000' AS time) AS smalldatetime),
CAST(CAST('15:59:00.0030000' AS time) AS smalldatetime)
The DATEADD/DATEDIFF is for truncating some time unit
Edit, misread questions
Just modify your current CAST
CAST(
DATEADD(minute,
DATEDIFF(minute,
0,
CAST(PA.ORA_INIZIO AS DATETIME)
) + 1,
0
)
AS TIME)

Don't know SQL Server well enough to answer off hand, but if no one comes by with a more more de facto way of doing this, then you could just add 1 minute to the value before rounding it down. Or add 0.999 minutes if you need to handle integer input values correctly as well.

If you wanted to round DATETIME d up to the nearest minute, you could do this:
CONVERT(DATETIME, CONVERT(SMALLDATETIME,
DATEADD(minute, CASE WHEN d = CONVERT(SMALLDATETIME, d) THEN 0 ELSE 1 END,
d)))

DECLARE # datetime = '2021-11-26 00:00:00.997'
SELECT dateadd(minute, ceiling(cast(# as float) * 1440),0) ceilingminute

Related

Combining Date and Time using DATEADD and DATEDIFF

I'm trying to create a new column by combining the date and time variables. For example, the data table looks like this and "StartDateTime" is the new variable I want to create.
Date StartTime *StartDateTime*
2014-03-20 1900-01-01 10:00:00.000 2014-03-30 10:00:00.000
2015-09-23 1900-01-01 11:00:00.000 2015-09-23 11:00:00.000
I used the cast function and it seems like my current code is working.
select *,
(cast(Date as datetime) + cast(StartTime as datetime)) as StartDateTime
from my_table
But I just saw this line of code on a random website and it seems like it does the same thing. However, I didn't really get the logic behind it.
select *,
DATEADD(day, 0, DATEDIFF(day, 0, Date)) + DATEADD(day, 0 -DATEDIFF(day, 0, StartTime), StartTime) As StartDateTime
from my_table
I believe the first part DATEADD(day, 0, DATEDIFF(day, 0, Date)) just returns the original date but I don't really get the second part. My understanding is DATEDIFF(day, 0, StartTime) would just return 0 and I'm not sure why 0 -DATEDIFF(day, 0, StartTime) is necessary.
Thank you.
This is one way to combine the date and time values into a single DateTime2:
declare #Samples as Table ( StartDate Date, StartTime DateTime2 );
insert into #Samples ( StartDate, StartTime ) values
( '2014-03-20', '1900-01-01 10:00:00.000' ),
( '2015-09-23', '1900-01-01 11:00:00.000' );
select StartDate, StartTime,
-- Demonstrate how to get the time with millisecond resolution from StartTime .
Cast( StartTime as Time(3) ) as StartTimeAsTime,
-- Combine the time from StartTime with the date from StartDate .
-- Get the time, convert it to milliseconds after midnight, and add it to the date as a DateTime2 .
DateAdd( ms, DateDiff( ms, 0, Cast( StartTime as Time(3) ) ), Cast( StartDate as DateTime2 ) ) as StartDateTime
from #Samples;
I have no idea what that code is doing. And the add operator is also not supported by all datetime datatypes. The correct solution is:
select *
, dateadd(second, datepart(second, StartTime), dateadd(minute, datepart(minute, StartTime), dateadd(hour, datepart(hour, StartTime), [Date])))
from (values (convert(datetime2(0),'2014-03-20'), convert(time,'10:00:00.000'))) as X ([Date], StartTime);
Ideally you would store your StartTime value in a time datatype. But the above code will still work with a datetime2 datetype (which is the recommended form of datetime to use).

Truncate timestamp

I would like into a stored procedure, truncate timestamp input values at the top hour or at the lower hour.
For example, if my input values are 2020-02-12 06:56:00 and 2020-02-12 07:14:00, I would like to transforme it in 2020-02-12 06:00:00 and 2020-02-12 08:00:00
Is a cast function can work?
You can construct the new datetimes from the parts that you want of your original datetimes.
declare #start datetime = '2020-02-12 06:56:00'
declare #end datetime = '2020-02-12 07:14:00'
select #start as OriginalStart,
#end as OriginalEnd,
datetimefromparts(year(#start), month(#start), day(#start), datepart(hour, #start), 0, 0, 0) as TruncatedStart,
dateadd(hour, 1, datetimefromparts(year(#end), month(#end), day(#end), datepart(hour, #end), 0, 0, 0)) as TruncatedEnd
The first truncation of the interval is the lower hour, and the second one adds an additional hour so it returns the higher hour.
PS: If what you want is to round to the nearest hour, then you can add 30 minutes and truncate :
declare #date datetime = '2020-02-12 06:56:00'
set #date = dateadd(minute, 30, #date)
select datetimefromparts(year(#date), month(#date), day(#date), datepart(hour, #date), 0, 0, 0) as NearestHour
or in a single step (using Lepetit's shortcut for truncation) :
declare #date datetime = '2020-02-12 06:56:00'
select dateadd(hour, datediff(hour, 0, dateadd(minute, 30, #date)), 0) AS NearestHour
This is a simpler solution:
declare #start datetime = '2020-02-12 06:56:00'
declare #end datetime = '2020-02-12 07:14:00'
select #start as OriginalStart,
#end as OriginalEnd,
dateadd(hour, datediff(hour, 0, #start), 0) as TruncatedStart,
dateadd(hour, datediff(hour, 0, dateadd(hour, 1, #end)), 0) as TruncatedEnd
In both cases the function substracts the hour part from the original timestamp. For the TruncatedEnd, one hour is added, so that the result is the subsequent hour.
Using a bit of arithmetic calculation, convert to hours with decimal and use floor() and ceiling() to perform the round up / down
first it find the time different with 00:00:00 in terms of second. convert(date, date_col) convert the datetime to date, so effectively it is 00:00:00
datediff(second, convert(date, date_col), date_col)
then you divide by 60 x 60 = 3600 seconds. Gives you fraction of hours
then you use floor() or ceiling() to perform the rounding
and lastly you add that back to the date (convert(date, date_col))
Final query
select *,
RoundDown = convert(datetime, convert(date, date_col))
+ dateadd(hour, floor(datediff(second, convert(date, date_col), date_col) / (3600.0)), 0),
RoundUp = convert(datetime, convert(date, date_col))
+ dateadd(hour, ceiling(datediff(second, convert(date, date_col), date_col) / (3600.0)), 0)
from (
values
('2020-02-12 06:56:00'),
('2020-02-12 07:14:00')
) d (date_col)
/*
2020-02-12 06:56:00 2020-02-12 06:00:00 2020-02-12 07:00:00
2020-02-12 07:14:00 2020-02-12 07:00:00 2020-02-12 08:00:00
*/
EDIT : a much simpler query below
find the different in minute divide by 60.0 minutes to get different in terms of hour (with decimal places) and then apply floor or ceiling. Finally add that result back
select getdate() as Now,
dateadd(hour, floor(datediff(minute, 0, getdate()) / 60.0), 0) as RoundDown,
dateadd(hour, ceiling(datediff(minute, 0, getdate()) / 60.0), 0) as RoundUp

Rounding time untill in SQL

I have a table with timestamp that I want to round off at 15 min. interval. I can round off using the below Query but it rounds off both 11:58 and 12:02 to 12:00 which is not what I want. I would like to round off timestamp at 15 min. interval which gives me time_untill ie for anything between 11:45 to 11:59 should be rounded off to 12 and anything between 12:00 to 12:14 should be rounded off to 12:15. Please let me know how can I achieve that? Thanks
SELECT transaction_id,
CONVERT(smalldatetime, ROUND(CONVERT(float, CONVERT(datetime, entry_date_time)) * 96.0, 0, 1) /96.0) as transaction_datetime
FROM <table>
You can use datetimefromparts():
select dateadd(minute,
15,
datetimefromparts(year(entry_date_time), month(entry_date_time), day(entry_date_time),
datepart(hour, entry_date_time),
15 * (datepart(minute, entry_date_time) / 15), 0, 0
)
) as roundup15
You could use the DATEADD/DATEDIFF method to truncate date/time values that's been available for a long time.
SELECT transaction_id,
entry_date_time,
DATEADD( MI, DATEDIFF( MI, '2010', entry_date_time)/15*15, '2010') as transaction_datetime
--FROM Sample Data
FROM (SELECT ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) transaction_id,
DATEADD( SS, CHECKSUM(NEWID())%10000, CAST( GETDATE() AS smalldatetime)) AS entry_date_time
FROM sys.columns)x;
Something like this...
DECLARE #time TIME(0) = GETDATE();
SELECT
DATEADD(MINUTE,
(((DATEDIFF(MINUTE, '00:00:00', #time) % 60) / 15) * 15),
DATEADD(HOUR, DATEDIFF(HOUR, '00:00:00', #time), '00:00:00')
);

Datediff function resulted in an overflow for two date Minute Diff (Without DateDiff_Big)

I am using SQL 2008/2012.
Query to calculate Minute Difference between two dates.
select DATEDIFF(mi, '9999-08-03 04:20:00.000', '2005-05-22 03:45:09.530')
Error:
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.
Note : DateDiff_Big not support this version.
Is there any other way to get result. without using DateDiff_Big
So you use a smaller unit and do some arithmetic. But I presume you mean:
select datediff(minute, '2005-05-22 03:45:09.530', '9999-08-03 04:20:00.000')
Normally, one wants the difference to be positive (although that is not related to the answer).
select (convert(bigint, datediff(day, '2100-08-03 04:20:00', '9999-08-03 04:20:00.000')) * 60 * 24) +
datediff(minute, '2005-05-22 03:45:09.530', '2100-08-03 04:20:00')
Does this fit your needs
DECLARE #DT1 datetime = '9999-08-03 04:20:00.000'
DECLARE #DT2 datetime = '2005-05-22 03:45:09.530'
select --DATEDIFF_BIG(mi, #DT1, #DT2),
CONVERT(BIGINT, DATEDIFF(DAY, #DT1, #DT2)) * 24 * 60
+ CONVERT(BIGINT, DATEDIFF(mi, CONVERT(TIME(7), #DT1), CONVERT(TIME(7), #DT2)))
GO
try this type to get Minutes Different
SELECT CONVERT(BIGINT, DATEDIFF(HOUR, '2005-05-22 03:45:09.530', '9999-08-03 04:20:00.000')) * 60
Check This.
SELECT
CAST(DATEDIFF(hour, '9999-08-03 04:20:00.000', '2009-05-22 03:45:09.530') AS BIGINT)* 60+
DATEDIFF(mi, CONVERT(TIME, '9999-08-03 04:20:00.000'),
CONVERT(TIME, '2009-05-22 03:45:09.530'))+60;
DATEDIFF has a limit that depends on what time element is used.
Because it can only return a number that fits in an INT.
For a return value out of range for int (-2,147,483,648 to
+2,147,483,647), DATEDIFF returns an error. For millisecond, the maximum difference between startdate and enddate is 24 days, 20 hours,
31 minutes and 23.647 seconds. For second, the maximum difference is
68 years.
For minutes that appears to be 4083 years.
So then limit for minutes would be in the -4083 years to 4083 years range.
Then you could wrap the DATEDIFF in a CASE WHEN that checks if it's in that range.
And let it default to the DATEDIFF in hours * 60.
It'll loose some minutes for the default, but that might still be better than returning a NULL.
SELECT dt1, dt2,
DATEDIFF(year, dt1, dt2) AS diff_years,
DATEDIFF(hour, dt1, dt2) AS diff_hours,
CONVERT(BIGINT,
CASE
WHEN DATEDIFF(year, dt1, dt2) BETWEEN -4083 AND 4083
THEN DATEDIFF(minute, dt1, dt2)
ELSE CONVERT(BIGINT,DATEDIFF(hour, dt1, dt2)) * 60
END) AS diff_minutes
FROM (VALUES
('9999-08-03 04:20:00.000', '2005-05-22 03:45:09.530'),
('6083-01-01 00:00:00.000', '2000-01-01 00:00:00.000'),
('2000-01-01 00:00:00.000','6083-01-01 00:00:00.000'),
('0001-01-01 00:00:00.000','9999-12-31 00:00:00.000')
) q(dt1, dt2)

Cast smalldatetime to real - results? SQL [duplicate]

why the out put of this query:
declare #currentDate as datetime
set #currentDate ='01/07/2010'
select convert(float, #currentdate)
...is 40183 ?
So for those who are getting confuse with my question, my question is How to know the result of above query without executing it ?
DateTime is often represented as a day count from a pre-determined date (generally know as the epoch) on the integer part and the percentage of the day elapsed since mid-night on the fractional part.
SQL Server is not the exception to this, thus the conversion to Float makes a lot of sense. Day 0 is Jan 01 1900 00:00:00 (AFAIK, in no particular time-zone, so you shall consider it "local time").
So, you can try this:
declare #ADate DateTime;
set #ADate = '19000101 00:00:00';
select CONVERT(float, #ADate); --should print 0
set #ADate = '19000101 12:00:00';
select CONVERT(float, #ADate); --should print 0.5
set #ADate = '19001231 06:00:00';
select CONVERT(float, #ADate); --should print 364.25
So, for your results, 40183 days has been passed since 01/01/1900 00:00:00 and 01/07/2010 00:00:00
Clarification: Unix like systems use a different approach to store datetimes: Seconds since Unix epoch (Jan 1 1970 00:00:00 UTC), which is more known as epoch time.
[Edit]
Date format on this response was changed to YYYYMMDD format on 20140416, after some new years of experience with SQL Server (and as #Damien said in his comment) this is the only safe format.
DateTime values are actually stored as two four-byte integers under the hood. The first four-byte integer represents the number of days since 1900-01-01. The second four-byte integer stores the number of milliseconds since midnight. When you convert a datetime into a float, the decimal portion represents the percentage of the 24-day that has past. Thus, 0.5 represents noon.
It's basically converting the datetime to an OLE Date. There's a decent description of the process in the documentation for System.DateTime.ToOADate():
http://msdn.microsoft.com/en-us/library/system.datetime.tooadate.aspx
The quick explanation is that the integer part is the number of days since 12/30/1899. The fractional part (zero in this case) is the time divided by 24.
This should help you understand the TSQL implementation (or implement your own):
DECLARE
#date DATETIME = '20180125 09:15:30.549',
#date_dec DECIMAL (26,10) = 43123.3857702546
SELECT
CAST(#date_dec AS DATETIME) AS [TSQL cast to DATETIME],
CAST(#date AS DECIMAL (26,10)) AS [TSQL cast to DECIMAL]
SELECT
DATEADD(DAY, FLOOR(#date_dec),
DATEADD(HOUR, FLOOR(#date_dec % 1 * 24),
DATEADD(MINUTE, FLOOR((#date_dec % 1 * 24) % 1 * 60),
DATEADD(SECOND, FLOOR(((#date_dec % 1 * 24) % 1 * 60) % 1 * 60),
DATEADD(MILLISECOND, FLOOR((((#date_dec % 1 * 24) % 1 * 60) % 1 * 60) % 1 * 1000), '19000101')
)
)
)
) AS [Manual cast to DATETIME],
DATEDIFF(DAY, '19000101', #date)
+ (
DATEPART(HOUR, #date)
+ (
DATEPART(MINUTE, #date)
+ (
DATEPART(SECOND, #date)
+ DATEPART(MILLISECOND, #date) / CAST(1000 AS FLOAT)
) / CAST(60 AS FLOAT)
) / CAST(60 AS FLOAT)
) / CAST(24 AS DECIMAL (26,10)) AS [Manual cast to DECIMAL]
Note that the result is not always the same as TSQL loses precision on last millisecond digit.