Why does DATEDIFF treat 23:59.59.999 as the next day? - sql

When I run the following:
DECLARE #date1 datetime = '2017-12-01 23:59:59.998'
DECLARE #date2 datetime = '2017-12-11 00:00:00.000'
SELECT DATEDIFF(day, #date1, #date2)
I get 10 as expected since it only compares the day part
However, if I add one millisecond:
DECLARE #date1 datetime = '2017-12-01 23:59:59.999'
DECLARE #date2 datetime = '2017-12-11 00:00:00.000'
SELECT DATEDIFF(day, #date1, #date2)
I get 9. It seems like SQL Server is rounding 23:59.59.999 to 24:00.00.000 and thus the next day but won't round up anything else. Is there any way to prevent this?

MS SQL Uses
4 bytes to store the days past Jan 1, 1900.
4 bytes to store the clock ticks past midnight. ( a tick is 3.3miliseconds)
What you are seeing is overflow. Since the number can not be represented by the 4 bytes, it will use the 5th byte thus incrementing the day by 1.
SQL Server stores the second integer for the time as the number of
clock ticks after midnight. A second contains 300 ticks, so a tick
equals 3.3 milliseconds (ms). You can see the values for days and
clock ticks by converting a datetime value to a binary(8) value and
using the substring function to extract each set of 4 bytes. The code
in Figure 3 then converts each set of 4 bytes into an integer.
Source: http://www.itprotoday.com/microsoft-sql-server/solving-datetime-mystery

Related

Changing UTC Time to Local Time using scalar-valued function with Select statements SQL

I've been trying to get this work over two days and still stuck. Any assistance or tips would be greatly appreciated.
Creating a function for the date conversion:
CREATE FUNCTION LocalDateFromUTCTime
(
#UTCDateTime datetime
)
RETURNS datetime
BEGIN
DECLARE #diff int;
SET #diff = datediff(hh,GetUTCDate(), GetDate());
RETURN DATEADD(day, DATEDIFF(day, 0, DATEADD(hh, #diff, #UTCDateTime)),0);
END
Then select SQL statement I'm trying to use with the function above is below:
SELECT
o.Number AS 'Order#',
o.[guid] as 'guid',
dbo.LocalDateFromUTCTime(o.settlementdate) as 'closing date'
FROM pf.order o
doing this displays the settlement date with no timestamp.
2015-07-15 00:00:00.000
How could I change this to show the correct Local time?
Thanks
Your date conversion is specifically returning the difference in days. The dateadd is adding the number of days between date 0 (which is actually 01-01-1900) and your UTC date onto a different date 0. This means that the hours and minutes are completely ignored.
If you want to include the hours and minutes you just need to drop the dateadd(...datediff( part:
RETURN DATEADD(hh, #diff, #UTCDateTime)

Substract Date from GETDATE() to get "time since"

I have a elements that have values in DATETIME format in column 'Date Created'.
I would like to be able to extract value how long time ago it was from time at which query is executed (as a reference take GETDATE())
When executing this:
UPDATE #LPs
SET LastUsed = (GETDATE() - DateCreated)
I am getting 1900-01-01, I believe that is due to SQL Server limitations for minimal DATETIME.
How should I right is so I can get values like "2 years 3 months", "2 months" (any datetime format)?
Sql server does not have a built in function that returns date differences as a formatted string like you want, but it does have a DATEDIFF function that will return the number of date parts between two dates:
DECLARE #Now datetime = GETDATE(),
#Date datetime = GETDATE() + 5 -- five days from now
SELECT #Now as Now,
#Date as Date,
DATEDIFF(Day, #Now, #Date) As DateDiff
You can get the number of months between the dates and calculate the string yourself using the modulu operator
SELECT DATEDIFF(day, DateCreated, GETDATE())
FROM table
maybe it works

Why does the Datediff function show different values?

When I am executing following query I am getting different results.
SELECT Datediff(year, 0, Getdate());
The result was 115
When I use this, I am getting another result:
SELECT Datediff(year, 1900, Getdate());
The result was 110
Actually in SQL Server it will take from 1900-01-01, but why do these show different values?
Try this to explain the logic:
select cast(0 as datetime)
select cast(1 as datetime)
An integer is interpreted as the number of Days since 1900-01-01 whereas a string value such as '1900' will be interpreted as a date format.
1900 Days from Jan 1st 1900 is 1905-03-16, which is five years from 1900 and 110 years from now (2015).
This is because if you cast 0 as datetime, it returns 1900 as the year part, whereas 1900 cast as datetime returns 1905 as the year part.
Demo
From MSDN:
Values with the datetime data type are stored internally by Microsoft SQL Server as two 4-byte integers. The first 4 bytes store the number of days before or after the base date, January 1, 1900. The base date is the system reference date.
That means, casting the literal 0 to datetime is equivalent to getting the datetime value for 0 days after 1/1/1900, which is 1/1/1900. Similarly for 1900. Therefore, as #MartinSmith points out in the comments, your calculation is equivalent to SELECT Datediff(year,dateadd(d,0,'1/1/1900'), Getdate()) which returns 115 as expected.
Possibly worth noting that the MSDN page on Cast and Convert does not specifically cover this scenario i.e. int to datetime.
The number you specified will be added as days which resulted in the difference.
Select DATEADD(dd,0,0)
Select DATEADD(dd,1900,0)
Result1 is 1900
Result2 is 1905.
So using them is equal to:
SELECT Datediff(year,0, Getdate()) = SELECT Datediff(year,DATEADD(dd,0,0), Getdate());
SELECT Datediff(year,1900, Getdate()) = SELECT Datediff(year,DATEADD(dd,1900,0), Getdate());;

SQL: Using DATEADD with bigints

I have some SQL to convert javascript dates to SQL dates which works great. However, I've encoutered some data which is too large and is causing an exception:
Arithmetic overflow error converting expression to data type int
Here is the SQL in question:
DATEADD(MILLISECOND, cast(569337307200000 as bigint) % 1000, DATEADD(SECOND, cast(569337307200000 as bigint) / 1000, '19700101'))
I am running this on SQL Server 2008.
Just do the problematic DATEADD in two steps, starting with a coarser time unit (seconds, minutes, hours etc.), then dropping back to the fine grained one for the remainder.
Avoid going to the level of weeks and months though as that would require actual calendar calculations and we would prefer the system to handle that.
Example below needs to calculate a start time given a (possibly) large current duration in milliseconds.
-- large durations can overflow the integer argument needed for DATEADD
-- so do as two steps subtracting minutes (60000ms) and then remaining milliseconds.
DATEADD(ms, -large_duration_ms%60000, DATEADD(minute, -large_duration_ms/60000, GETDATE()))
One way I got around the Integer overflow issue was to subtract a more recent date from the microtime unix time stamp.
DATEADD(s, (CreationTimeStamp/1000-1384128000), '2013-11-11') AS CreateDate,
This will not fix the OP's problem because they will still overflow the max on the date column.
According to MSDN, in DATEADD (datepart , number , date )
number is an expression that can be resolved to an int that is added
to a datepart of date. User-defined variables are valid. If you
specify a value with a decimal fraction, the fraction is truncated and
not rounded.
Also notice that even if you give number as an integer, depending on your date & datepart, it could overflow the max range of the date which is 31-12-9999 for sql server 2008
Number has to be an integer. Here is a Test Demo
I had the same problem and I wanted to be meet the datetime range of mssql
Minimun datetime: 1753-01-01 00:00:00.000 (-6847804800)
Maximum datetime: 9999-12-31 23:59:59.997 (253402300799)
To achieve this the only solution I found was to loop to use DATEADD with int range values.
So based on this answer: https://stackoverflow.com/a/2904294/687490
CREATE FUNCTION dbo.fn_ConvertToBigDateTime (#Datetime BIGINT)
RETURNS DATETIME
AS
BEGIN
DECLARE #result datetime = Convert(datetime, '01/01/1970');
DECLARE #LocalTimeOffset BIGINT
,#AdjustedLocalDatetime BIGINT
,#MinIntValue INT
,#MaxIntValue INT
,#RemainingSeconds BIGINT;
-- define int limit
SET #MinIntValue = -2147483648;
SET #MaxIntValue = 2147483647;
-- compute the datetime with the offset
SET #LocalTimeOffset = DATEDIFF(second,GETDATE(),GETUTCDATE())
SET #AdjustedLocalDatetime = #Datetime - #LocalTimeOffset
-- going to the future
WHILE(#AdjustedLocalDatetime>#MaxIntValue)
BEGIN
SET #AdjustedLocalDatetime = #AdjustedLocalDatetime - #MaxIntValue;
SELECT #result = Convert(datetime, dateadd(ss, #MaxIntValue,#result));
END
-- going back in the past
WHILE(#AdjustedLocalDatetime<#MinIntValue)
BEGIN
SET #AdjustedLocalDatetime = #AdjustedLocalDatetime - #MinIntValue;
SELECT #result = Convert(datetime, dateadd(ss, #MinIntValue,#result));
END
RETURN (SELECT DATEADD(second,#AdjustedLocalDatetime, #result))
END;
You can then test the function with :
select dbo.fn_ConvertToBigDateTime(-6847804800) as 'min datetime',
dbo.fn_ConvertToBigDateTime(253402300799) as 'max datetime'
Hope it will help.
You can try converting the millis to days, add the days to beginning of EPOCH, and add the ms part to the date at the end. The problem is that you were trying to convert millis to seconds, which can still be too large number for INT for larger dates.
DATEADD(MILLISECOND,
CAST(myLongDateMs AS BIGINT) % 86400000,
DATEADD(day,
CAST(myLongDateMs AS BIGINT) / 86400000,
'19700101'
)
)
I faced this problem too. In my sql statement, the error occurred when the date time value is null.
My solution is to check whether the date time value is null using "CASE When". Only running the arithmetic when it is not null, and the problem solved.

Datetime comparison error in sql?

I have used date and time validation for scheduling a report...I have to schedule that reports for future date and time only and not previous date and time..I have used this
declare #Dt varchar(50)
declare #Hr varchar(50)
declare #trandate_time_tmp as TIME(0)
select #trandate_time_tmp = getdate()
set #Dt = DATEDIFF (D,#schedule_date ,#trandate_tmp )
set #Hr = DATEDIFF (S,#schedule_date ,#trandate_time_tmp )
if ( #Dt > 0)
begin
raiserror('Schedule Date should not be earlier than system date',16,1)
return
end
if ( #Hr > 0)
begin
raiserror('Schedule Time should not be earlier than system time',16,1)
return
end
For date part it is checking correctly but for time it is throwing error as
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.
Not exactly answering your question, but perhaps a solution to your problem. You don't need to use DATEDIFF and check the results, you could just compare the two dates.
IF ( #schedule_date <= GETDATE() )
BEGIN
RAISERROR('Schedule date should not be earlier than system date', 16, 1)
RETURN
END
I just ran into this same problem when trying to make a Unix timestamp from a date,
Here's an example of what I was trying to do:
select DATEDIFF(second,'1970-01-01','2200-01-11');
It overflows since DATEDIFF is trying to return a signed integer - which can only hold just over 68 years worth of seconds.
In order to get the Unix timestamp (which I need so I can feed it into Sphinx Search), you can get the difference in minutes first, then cast the result as a big integer and then multiply by 60 seconds:
select CAST(DATEDIFF(minute,'1970-01-01','2200-01-11') AS BIGINT) * 60;
Now we should be able to handle dates that vary in difference of up to 4000 years or so. If you need even more room, simply change out minute with bigger and bigger intervals, and change the seconds multiplier accordingly.