Floor a date in SQL server - sql-server

In SQL Server, how do I "floor" a DATETIME to the second/minute/hour/day/year?
Let's say that I have a date of 2008-09-17 12:56:53.430, then the output of flooring should be:
Year: 2008-01-01 00:00:00.000
Month: 2008-09-01 00:00:00.000
Day: 2008-09-17 00:00:00.000
Hour: 2008-09-17 12:00:00.000
Minute: 2008-09-17 12:56:00.000
Second: 2008-09-17 12:56:53.000

The key is to use DATEADD and DATEDIFF along with the appropriate SQL timespan enumeration.
declare #datetime datetime;
set #datetime = getdate();
select #datetime;
select dateadd(year,datediff(year,0,#datetime),0);
select dateadd(month,datediff(month,0,#datetime),0);
select dateadd(day,datediff(day,0,#datetime),0);
select dateadd(hour,datediff(hour,0,#datetime),0);
select dateadd(minute,datediff(minute,0,#datetime),0);
select dateadd(second,datediff(second,'2000-01-01',#datetime),'2000-01-01');
select dateadd(week,datediff(week,0,#datetime),-1); --Beginning of week is Sunday
select dateadd(week,datediff(week,0,#datetime),0); --Beginning of week is Monday
Note that when you are flooring by the second, you will often get an arithmetic overflow if you use 0. So pick a known value that is guaranteed to be lower than the datetime you are attempting to floor.

In SQL Server here's a little trick to do that:
SELECT CAST(FLOOR(CAST(CURRENT_TIMESTAMP AS float)) AS DATETIME)
You cast the DateTime into a float, which represents the Date as the integer portion and the Time as the fraction of a day that's passed. Chop off that decimal portion, then cast that back to a DateTime, and you've got midnight at the beginning of that day.
This is probably more efficient than all the DATEADD and DATEDIFF stuff. It's certainly way easier to type.

Expanding upon the Convert/Cast solution, in Microsoft SQL Server 2008 you can do the following:
cast(cast(getdate() as date) as datetime)
Just replace getdate() with any column which is a datetime.
There are no strings involved in this conversion.
This is ok for ad-hoc queries or updates, but for key joins or heavily used processing it may be better to handle the conversion within the processing or redefine the tables to have appropriate keys and data.
In 2005, you can use the messier floor: cast(floor(cast(getdate() as float)) as datetime)
I don't think that uses string conversion either, but I can't speak to comparing actual efficiency versus armchair estimates.

I've used #Portman's answer many times over the years as a reference when flooring dates and have moved its working into a function which you may find useful.
I make no claims to its performance and merely provide it as a tool for the user.
I ask that, if you do decide to upvote this answer, please also upvote #Portman's answer, as my code is a derivative of his.
IF OBJECT_ID('fn_FloorDate') IS NOT NULL DROP FUNCTION fn_FloorDate
SET ANSI_NULLS OFF
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE FUNCTION [dbo].[fn_FloorDate] (
#Date DATETIME = NULL,
#DatePart VARCHAR(6) = 'day'
)
RETURNS DATETIME
AS
BEGIN
IF (#Date IS NULL)
SET #Date = GETDATE();
RETURN
CASE
WHEN LOWER(#DatePart) = 'year' THEN DATEADD(YEAR, DATEDIFF(YEAR, 0, #Date), 0)
WHEN LOWER(#DatePart) = 'month' THEN DATEADD(MONTH, DATEDIFF(MONTH, 0, #Date), 0)
WHEN LOWER(#DatePart) = 'day' THEN DATEADD(DAY, DATEDIFF(DAY, 0, #Date), 0)
WHEN LOWER(#DatePart) = 'hour' THEN DATEADD(HOUR, DATEDIFF(HOUR, 0, #Date), 0)
WHEN LOWER(#DatePart) = 'minute' THEN DATEADD(MINUTE, DATEDIFF(MINUTE, 0, #Date), 0)
WHEN LOWER(#DatePart) = 'second' THEN DATEADD(SECOND, DATEDIFF(SECOND, '2000-01-01', #Date), '2000-01-01')
ELSE DATEADD(DAY, DATEDIFF(DAY, 0, #Date), 0)
END;
END
Usage:
DECLARE #date DATETIME;
SET #date = '2008-09-17 12:56:53.430';
SELECT
#date AS [Now],--2008-09-17 12:56:53.430
dbo.fn_FloorDate(#date, 'year') AS [Year],--2008-01-01 00:00:00.000
dbo.fn_FloorDate(default, default) AS [NoParams],--2013-11-05 00:00:00.000
dbo.fn_FloorDate(#date, default) AS [ShouldBeDay],--2008-09-17 00:00:00.000
dbo.fn_FloorDate(#date, 'month') AS [Month],--2008-09-01 00:00:00.000
dbo.fn_FloorDate(#date, 'day') AS [Day],--2008-09-17 00:00:00.000
dbo.fn_FloorDate(#date, 'hour') AS [Hour],--2008-09-17 12:00:00.000
dbo.fn_FloorDate(#date, 'minute') AS [Minute],--2008-09-17 12:56:00.000
dbo.fn_FloorDate(#date, 'second') AS [Second];--2008-09-17 12:56:53.000

The CONVERT() function can do this as well, depending on what style you use.

Too bad it's not Oracle, or else you could use trunc() or to_char().
But I had similar issues with SQL Server and used the CONVERT() and DateDiff() methods, as referenced here

There are several ways to skin this cat =)
select convert(datetime,convert(varchar,CURRENT_TIMESTAMP,101))

DateAdd along with DateDiff can help to do many different tasks. For example, you can find last day of any month as well can find last day of previous or next month.
----Last Day of Previous Month
SELECT DATEADD(s,-1,DATEADD(mm, DATEDIFF(m,0,GETDATE()),0))
LastDay_PreviousMonth
----Last Day of Current Month
SELECT DATEADD(s,-1,DATEADD(mm, DATEDIFF(m,0,GETDATE())+1,0))
LastDay_CurrentMonth
----Last Day of Next Month
SELECT DATEADD(s,-1,DATEADD(mm, DATEDIFF(m,0,GETDATE())+2,0))
LastDay_NextMonth
Source

Since PostgreSQL is also a "SQL Server", I'll mention
date_trunc()
Which does exactly what you're asking gracefully.
For example:
select date_trunc('hour',current_timestamp);
date_trunc
------------------------
2009-02-18 07:00:00-08
(1 row)

Related

Selecting column values based on minutes [duplicate]

In SQL server 2008, I would like to get datetime column rounded to nearest hour and nearest minute preferably with existing functions in 2008.
For this column value 2007-09-22 15:07:38.850, the output will look like:
2007-09-22 15:08 -- nearest minute
2007-09-22 15 -- nearest hour
declare #dt datetime
set #dt = '09-22-2007 15:07:38.850'
select dateadd(mi, datediff(mi, 0, #dt), 0)
select dateadd(hour, datediff(hour, 0, #dt), 0)
will return
2007-09-22 15:07:00.000
2007-09-22 15:00:00.000
The above just truncates the seconds and minutes, producing the results asked for in the question. As #OMG Ponies pointed out, if you want to round up/down, then you can add half a minute or half an hour respectively, then truncate:
select dateadd(mi, datediff(mi, 0, dateadd(s, 30, #dt)), 0)
select dateadd(hour, datediff(hour, 0, dateadd(mi, 30, #dt)), 0)
and you'll get:
2007-09-22 15:08:00.000
2007-09-22 15:00:00.000
Before the date data type was added in SQL Server 2008, I would use the above method to truncate the time portion from a datetime to get only the date. The idea is to determine the number of days between the datetime in question and a fixed point in time (0, which implicitly casts to 1900-01-01 00:00:00.000):
declare #days int
set #days = datediff(day, 0, #dt)
and then add that number of days to the fixed point in time, which gives you the original date with the time set to 00:00:00.000:
select dateadd(day, #days, 0)
or more succinctly:
select dateadd(day, datediff(day, 0, #dt), 0)
Using a different datepart (e.g. hour, mi) will work accordingly.
"Rounded" down as in your example. This will return a varchar value of the date.
DECLARE #date As DateTime2
SET #date = '2007-09-22 15:07:38.850'
SELECT CONVERT(VARCHAR(16), #date, 120) --2007-09-22 15:07
SELECT CONVERT(VARCHAR(13), #date, 120) --2007-09-22 15
I realize this question is ancient and there is an accepted and an alternate answer. I also realize that my answer will only answer half of the question, but for anyone wanting to round to the nearest minute and still have a datetime compatible value using only a single function:
CAST(YourValueHere as smalldatetime);
For hours or seconds, use Jeff Ogata's answer (the accepted answer) above.
Select convert(char(8), DATEADD(MINUTE, DATEDIFF(MINUTE, 0, getdate), 0), 108) as Time
will round down seconds to 00

Week start date and week end date calculated wrong

I have a query for calculating first and last date in the week, according to given date. It is enough to set #dDate and the query will calculate first (monday) and last date (sunday) for that week.
Problem is, that is calculating wrong and I don't understand why.
Example:
#dDate = 2019-10-03 (year-month-day).
Result:
W_START W_END
2019-09-25 2019-10-01
But it should be:
2019-09-30 2019-10-06
Why is that?
Query:
set datefirst 1
declare #dDate date = cast('2019-10-16' as date)
select #dDAte
declare #year int = (select DATEPART(year, #dDAte))
select #year
declare #StartingDate date = cast(('' + cast(#year as nvarchar(4)) + '-01-01') as date)
select #StartingDate
declare #dateWeekEnd date = (select DATEADD(week, (datepart(week, cast(#dDate as date)) - 1), #StartingDate))
declare #dateWeekStart date = dateadd(day, -6, #dateWeekEnd)
select #dateWeekStart W_START, #dateWeekEnd W_END
Days of the week are so complicated. I find it easier to remember that 2001-01-01 fell on a Monday.
Then, the following date arithmetic does what you want:
select dateadd(day,
7 * (datediff(day, '2001-01-01', #dDate) / 7),
'2001-01-01' -- 2001-01-01 fell on a Monday
)
I admit this is something of a cop-out/hack. But SQL Server -- and other databases -- make such date arithmetic so cumbersome that simple tricks like this are handy to keep in mind.

SQL Server - Finding the first day of the week

I've been looking around for a chunk of code to find the first day of the current week, and everywhere I look I see this:
DATEADD(WK, DATEDIFF(WK,0,GETDATE()),0)
Every place says this is the code I'm looking for.
The problem with this piece of code is that if you run it for Sunday it chooses the following Monday.
If I run:
SELECT GetDate() , DATEADD(WK, DATEDIFF(WK,0,GETDATE()),0)
Results for today (Tuesday):
2013-05-14 09:36:39.650................2013-05-13 00:00:00.000
This is correct, it chooses Monday the 13th.
If I run:
SELECT GetDate()-1 , DATEADD(WK, DATEDIFF(WK,0,GETDATE()-1),0)
Results for yesterday (Monday):
2013-05-13 09:38:57.950................2013-05-13 00:00:00.000
This is correct, it chooses Monday the 13th.
If I run:
SELECT GetDate()-2 , DATEADD(WK, DATEDIFF(WK,0,GETDATE()-2),0)
Results for the 12th (Sunday):
2013-05-12 09:40:14.817................2013-05-13 00:00:00.000
This is NOT correct, it chooses Monday the 13th when it should choose the previous Monday, the 6th.
Can anyone illuminate me as to what's going in here? I find it hard to believe that no one has pointed out that this doesn't work, so I'm wondering what I'm missing.
It is DATEDIFF that returns the "incorrect" difference of weeks, which in the end results in the wrong Monday. And that is because DATEDIFF(WEEK, ...) doesn't respect the DATEFIRST setting, which I'm assuming you have set to 1 (Monday), and instead always considers the week crossing to be from Saturday to Sunday, or, in other words, it unconditionally considers Sunday to be the first day of the week in this context.
As for an explanation for that, so far I haven't been able to find an official one, but I believe this must have something to do with the DATEDIFF function being one of those SQL Server treats as always deterministic. Apparently, if DATEDIFF(WEEK, ...) relied on the DATEFIRST, it could no longer be considered always deterministic, which I can only guess wasn't how the developers of SQL Server wanted it.
To find the first day of the week's date, I would (and most often do actually) use the first suggestion in #Jasmina Shevchenko's answer:
DATEADD(DAY, 1 - DATEPART(WEEKDAY, #Date), #Date)
DATEPART does respect the DATEFIRST setting and (most likely as a result) it is absent from the list of always deterministic functions.
Try this one -
SET DATEFIRST 1
DECLARE #Date DATETIME
SELECT #Date = GETDATE()
SELECT CAST(DATEADD(DAY, 1 - DATEPART(WEEKDAY, #Date), #Date) AS DATE)
SELECT CAST(#Date - 2 AS DATE), CAST(DATEADD(WK, DATEDIFF(WK, 0, #Date-2), 0) AS DATE)
Results:
---------- ----------
2013-05-12 2013-05-13
SQL Server has a SET DATEFIRST function which allows you to tell it what the first day of the week should be. SET DATEFIRST = 1 tells it to consider Monday as the first day of the week. You should check what the server's default setting is via ##DATEFIRST. Or you could simply change it at the start of your query.
Some references:
MSDN
Similar Question
That worked for me like a charm:
Setting moday as first day of the week without changing DATEFIRST variable:
-- FirstDayWeek
select dateadd(dd,(datepart(dw, case datepart(dw, [yourDate]) when 1 then dateadd(dd,-1,[yourDate]) else [yourDate] end) * -1) + 2, case datepart(dw, [yourDate]) when 1 then dateadd(dd,-1,[yourDate]) else [yourDate] end) as FirstDayWeek;
-- LastDayWeek
select dateadd(dd, (case datepart(dw, [yourDate]) when 1 then datepart(dw, dateadd(dd,-1,[yourDate])) else datepart(dw, [yourDate]) end * -1) + 8, case datepart(dw, [yourDate]) when 1 then dateadd(dd,-1,[yourDate]) else [yourDate] end) as LastDayWeek;
Setting sunday as fist day of the week without changing DATEFIRST variable
select convert(varchar(50), dateadd(dd, (datepart(dw, [yourDate]) * -1) + 2, [yourDate]), 103) as FirstDayWeek, convert(varchar(50), dateadd(dd, (datepart(dw, [yourDate]) * -1) + 8, [yourDate]), 103) as LastDayWeek;
You can change [yourDate] by GETDATE() for testing

Get first day of week in SQL Server

I am trying to group records by week, storing the aggregated date as the first day of the week. However, the standard technique I use for rounding off dates does not appear to work correctly with weeks (though it does for days, months, years, quarters and any other timeframe I've applied it to).
Here is the SQL:
select "start_of_week" = dateadd(week, datediff(week, 0, getdate()), 0);
This returns 2011-08-22 00:00:00.000, which is a Monday, not a Sunday. Selecting ##datefirst returns 7, which is the code for Sunday, so the server is setup correctly in as far as I know.
I can bypass this easily enough by changing the above code to:
select "start_of_week" = dateadd(week, datediff(week, 0, getdate()), -1);
But the fact that I have to make such an exception makes me a little uneasy. Also, apologies if this is a duplicate question. I found some related questions but none that addressed this aspect specifically.
To answer why you're getting a Monday and not a Sunday:
You're adding a number of weeks to the date 0. What is date 0? 1900-01-01. What was the day on 1900-01-01? Monday. So in your code you're saying, how many weeks have passed since Monday, January 1, 1900? Let's call that [n]. Ok, now add [n] weeks to Monday, January 1, 1900. You should not be surprised that this ends up being a Monday. DATEADD has no idea that you want to add weeks but only until you get to a Sunday, it's just adding 7 days, then adding 7 more days, ... just like DATEDIFF only recognizes boundaries that have been crossed. For example, these both return 1, even though some folks complain that there should be some sensible logic built in to round up or down:
SELECT DATEDIFF(YEAR, '2010-01-01', '2011-12-31');
SELECT DATEDIFF(YEAR, '2010-12-31', '2011-01-01');
To answer how to get a Sunday:
If you want a Sunday, then pick a base date that's not a Monday but rather a Sunday. For example:
DECLARE #dt DATE = '1905-01-01';
SELECT [start_of_week] = DATEADD(WEEK, DATEDIFF(WEEK, #dt, CURRENT_TIMESTAMP), #dt);
This will not break if you change your DATEFIRST setting (or your code is running for a user with a different setting) - provided that you still want a Sunday regardless of the current setting. If you want those two answers to jive, then you should use a function that does depend on the DATEFIRST setting, e.g.
SELECT DATEADD(DAY, 1-DATEPART(WEEKDAY, CURRENT_TIMESTAMP), CURRENT_TIMESTAMP);
So if you change your DATEFIRST setting to Monday, Tuesday, what have you, the behavior will change. Depending on which behavior you want, you could use one of these functions:
CREATE FUNCTION dbo.StartOfWeek1 -- always a Sunday
(
#d DATE
)
RETURNS DATE
AS
BEGIN
RETURN (SELECT DATEADD(WEEK, DATEDIFF(WEEK, '19050101', #d), '19050101'));
END
GO
...or...
CREATE FUNCTION dbo.StartOfWeek2 -- always the DATEFIRST weekday
(
#d DATE
)
RETURNS DATE
AS
BEGIN
RETURN (SELECT DATEADD(DAY, 1-DATEPART(WEEKDAY, #d), #d));
END
GO
Now, you have plenty of alternatives, but which one performs best? I'd be surprised if there would be any major differences but I collected all the answers provided so far and ran them through two sets of tests - one cheap and one expensive. I measured client statistics because I don't see I/O or memory playing a part in the performance here (though those may come into play depending on how the function is used). In my tests the results are:
"Cheap" assignment query:
Function - client processing time / wait time on server replies / total exec time
Gandarez - 330/2029/2359 - 0:23.6
me datefirst - 329/2123/2452 - 0:24.5
me Sunday - 357/2158/2515 - 0:25.2
trailmax - 364/2160/2524 - 0:25.2
Curt - 424/2202/2626 - 0:26.3
"Expensive" assignment query:
Function - client processing time / wait time on server replies / total exec time
Curt - 1003/134158/135054 - 2:15
Gandarez - 957/142919/143876 - 2:24
me Sunday - 932/166817/165885 - 2:47
me datefirst - 939/171698/172637 - 2:53
trailmax - 958/173174/174132 - 2:54
I can relay the details of my tests if desired - stopping here as this is already getting quite long-winded. I was a bit surprised to see Curt's come out as the fastest at the high end, given the number of calculations and inline code. Maybe I'll run some more thorough tests and blog about it... if you guys don't have any objections to me publishing your functions elsewhere.
For these that need to get:
Monday = 1 and Sunday = 7:
SELECT 1 + ((5 + DATEPART(dw, GETDATE()) + ##DATEFIRST) % 7);
Sunday = 1 and Saturday = 7:
SELECT 1 + ((6 + DATEPART(dw, GETDATE()) + ##DATEFIRST) % 7);
Above there was a similar example, but thanks to double "%7" it would be much slower.
For those who need the answer at work and creating function is forbidden by your DBA, the following solution will work:
select *,
cast(DATEADD(day, -1*(DATEPART(WEEKDAY, YouDate)-1), YourDate) as DATE) as WeekStart
From.....
This gives the start of that week. Here I assume that Sundays are the start of weeks. If you think that Monday is the start, you should use:
select *,
cast(DATEADD(day, -1*(DATEPART(WEEKDAY, YouDate)-2), YourDate) as DATE) as WeekStart
From.....
This works wonderfully for me:
CREATE FUNCTION [dbo].[StartOfWeek]
(
#INPUTDATE DATETIME
)
RETURNS DATETIME
AS
BEGIN
-- THIS does not work in function.
-- SET DATEFIRST 1 -- set monday to be the first day of week.
DECLARE #DOW INT -- to store day of week
SET #INPUTDATE = CONVERT(VARCHAR(10), #INPUTDATE, 111)
SET #DOW = DATEPART(DW, #INPUTDATE)
-- Magic convertion of monday to 1, tuesday to 2, etc.
-- irrespect what SQL server thinks about start of the week.
-- But here we have sunday marked as 0, but we fix this later.
SET #DOW = (#DOW + ##DATEFIRST - 1) %7
IF #DOW = 0 SET #DOW = 7 -- fix for sunday
RETURN DATEADD(DD, 1 - #DOW,#INPUTDATE)
END
Maybe you need this:
SELECT DATEADD(DD, 1 - DATEPART(DW, GETDATE()), GETDATE())
Or
DECLARE #MYDATE DATETIME
SET #MYDATE = '2011-08-23'
SELECT DATEADD(DD, 1 - DATEPART(DW, #MYDATE), #MYDATE)
Function
CREATE FUNCTION [dbo].[GetFirstDayOfWeek]
( #pInputDate DATETIME )
RETURNS DATETIME
BEGIN
SET #pInputDate = CONVERT(VARCHAR(10), #pInputDate, 111)
RETURN DATEADD(DD, 1 - DATEPART(DW, #pInputDate),
#pInputDate)
END
GO
Googled this script:
create function dbo.F_START_OF_WEEK
(
#DATE datetime,
-- Sun = 1, Mon = 2, Tue = 3, Wed = 4
-- Thu = 5, Fri = 6, Sat = 7
-- Default to Sunday
#WEEK_START_DAY int = 1
)
/*
Find the fisrt date on or before #DATE that matches
day of week of #WEEK_START_DAY.
*/
returns datetime
as
begin
declare #START_OF_WEEK_DATE datetime
declare #FIRST_BOW datetime
-- Check for valid day of week
if #WEEK_START_DAY between 1 and 7
begin
-- Find first day on or after 1753/1/1 (-53690)
-- matching day of week of #WEEK_START_DAY
-- 1753/1/1 is earliest possible SQL Server date.
select #FIRST_BOW = convert(datetime,-53690+((#WEEK_START_DAY+5)%7))
-- Verify beginning of week not before 1753/1/1
if #DATE >= #FIRST_BOW
begin
select #START_OF_WEEK_DATE =
dateadd(dd,(datediff(dd,#FIRST_BOW,#DATE)/7)*7,#FIRST_BOW)
end
end
return #START_OF_WEEK_DATE
end
go
http://www.sqlteam.com/forums/topic.asp?TOPIC_ID=47307
CREATE FUNCTION dbo.fnFirstWorkingDayOfTheWeek
(
#currentDate date
)
RETURNS INT
AS
BEGIN
-- get DATEFIRST setting
DECLARE #ds int = ##DATEFIRST
-- get week day number under current DATEFIRST setting
DECLARE #dow int = DATEPART(dw,#currentDate)
DECLARE #wd int = 1+(((#dow+#ds) % 7)+5) % 7 -- this is always return Mon as 1,Tue as 2 ... Sun as 7
RETURN DATEADD(dd,1-#wd,#currentDate)
END
For the basic (the current week's Sunday)
select cast(dateadd(day,-(datepart(dw,getdate())-1),getdate()) as date)
If previous week:
select cast(dateadd(day,-(datepart(dw,getdate())-1),getdate()) -7 as date)
Internally, we built a function that does it but if you need quick and dirty, this will do it.
Since Julian date 0 is a Monday just add the number of weeks to Sunday
which is the day before -1 Eg. select dateadd(wk,datediff(wk,0,getdate()),-1)
I found some of the other answers long-winded or didn't actually work if you wanted Monday as the start of the week.
Sunday
SELECT DATEADD(week, DATEDIFF(week, -1, GETDATE()), -1) AS Sunday;
Monday
SELECT DATEADD(week, DATEDIFF(week, 0, GETDATE() - 1), 0) AS Monday;
Set DateFirst 1;
Select
Datepart(wk, TimeByDay) [Week]
,Dateadd(d,
CASE
WHEN Datepart(dw, TimeByDay) = 1 then 0
WHEN Datepart(dw, TimeByDay) = 2 then -1
WHEN Datepart(dw, TimeByDay) = 3 then -2
WHEN Datepart(dw, TimeByDay) = 4 then -3
WHEN Datepart(dw, TimeByDay) = 5 then -4
WHEN Datepart(dw, TimeByDay) = 6 then -5
WHEN Datepart(dw, TimeByDay) = 7 then -6
END
, TimeByDay) as StartOfWeek
from TimeByDay_Tbl
This is my logic. Set the first of the week to be Monday then calculate what is the day of the week a give day is, then using DateAdd and Case I calculate what the date would have been on the previous Monday of that week.
This is a useful function for me
/* MeRrais 211126
select [dbo].[SinceWeeks](0,NULL)
select [dbo].[SinceWeeks](5,'2021-08-31')
*/
alter Function [dbo].[SinceWeeks](#Weeks int, #From datetime=NULL)
Returns date
AS
Begin
if #From is null
set #From=getdate()
return cast(dateadd(day, -(#Weeks*7+datepart(dw,#From)-1), #From) as date)
END
I don't have any issues with any of the answers given here, however I do think mine is a lot simpler to implement, and understand. I have not run any performance tests on it, but it should be neglegable.
So I derived my answer from the fact that dates are stored in SQL server as integers, (I am talking about the date component only). If you don't believe me, try this SELECT CONVERT(INT, GETDATE()), and vice versa.
Now knowing this, you can do some cool math equations. You might be able to come up with a better one, but here is mine.
/*
TAKEN FROM http://msdn.microsoft.com/en-us/library/ms181598.aspx
First day of the week is
1 -- Monday
2 -- Tuesday
3 -- Wednesday
4 -- Thursday
5 -- Friday
6 -- Saturday
7 (default, U.S. English) -- Sunday
*/
--Offset is required to compensate for the fact that my ##DATEFIRST setting is 7, the default.
DECLARE #offSet int, #testDate datetime
SELECT #offSet = 1, #testDate = GETDATE()
SELECT CONVERT(DATETIME, CONVERT(INT, #testDate) - (DATEPART(WEEKDAY, #testDate) - #offSet))
I had a similar problem. Given a date, I wanted to get the date of the Monday of that week.
I used the following logic: Find the day number in the week in the range of 0-6, then subtract that from the originay date.
I used: DATEADD(day,-(DATEPART(weekday,)+5)%7,)
Since DATEPRRT(weekday,) returns 1 = Sundaye ... 7=Saturday,
DATEPART(weekday,)+5)%7 returns 0=Monday ... 6=Sunday.
Subtracting this number of days from the original date gives the previous Monday. The same technique could be used for any starting day of the week.
I found this simple and usefull. Works even if first day of week is Sunday or Monday.
DECLARE #BaseDate AS Date
SET #BaseDate = GETDATE()
DECLARE #FisrtDOW AS Date
SELECT #FirstDOW = DATEADD(d,DATEPART(WEEKDAY,#BaseDate) *-1 + 1, #BaseDate)
Maybe I'm over simplifying here, and that may be the case, but this seems to work for me. Haven't ran into any problems with it yet...
CAST('1/1/' + CAST(YEAR(GETDATE()) AS VARCHAR(30)) AS DATETIME) + (DATEPART(wk, YOUR_DATE) * 7 - 7) as 'FirstDayOfWeek'
CAST('1/1/' + CAST(YEAR(GETDATE()) AS VARCHAR(30)) AS DATETIME) + (DATEPART(wk, YOUR_DATE) * 7) as 'LastDayOfWeek'

Deterministic scalar function to get day of week for a date

SQL Server, trying to get day of week via a deterministic UDF.
Im sure this must be possible, but cant figure it out.
UPDATE: SAMPLE CODE..
CREATE VIEW V_Stuff WITH SCHEMABINDING AS
SELECT
MD.ID,
MD.[DateTime]
...
dbo.FN_DayNumeric_DateTime(MD.DateTime) AS [Day],
dbo.FN_TimeNumeric_DateTime(MD.DateTime) AS [Time],
...
FROM {SOMEWHERE}
GO
CREATE UNIQUE CLUSTERED INDEX V_Stuff_Index ON V_Stuff (ID, [DateTime])
GO
Ok, i figured it..
CREATE FUNCTION [dbo].[FN_DayNumeric_DateTime]
(#DT DateTime)
RETURNS INT WITH SCHEMABINDING
AS
BEGIN
DECLARE #Result int
DECLARE #FIRST_DATE DATETIME
SELECT #FIRST_DATE = convert(DATETIME,-53690+((7+5)%7),112)
SET #Result = datediff(dd,dateadd(dd,(datediff(dd,#FIRST_DATE,#DT)/7)*7,#FIRST_DATE), #DT)
RETURN (#Result)
END
GO
Slightly similar approach to aforementioned solution, but just a one-liner that could be used inside a function or inline for computed column.
Assumptions:
You don't have dates before
1899-12-31 (which is a Sunday)
You want to imitate ##datefirst = 7
#dt is smalldatetime, datetime,
date, or datetime2 data type
If you'd rather it be different, change the date '18991231' to a date with the weekday that you'd like to equal 1. The convert() function is key to making the whole thing work - cast does NOT do the trick:
((datediff(day, convert(datetime,
'18991231', 112), #dt) % 7)
+ 1)
I know this post is way-super-old, but I was trying to do a similar thing and came up with a different solution and figured I'd post for posterity. Plus I did some searching around and did not find much content on this question.
In my case, I was trying to use a computed column PERSISTED, which requires the calculation to be deterministic. The calculation I used is:
datediff(dd,'2010-01-03',[DateColumn]) % 7 + 1
The idea is to figure out a known Sunday that you know will occur before any possible date in your table (in this case, Jan 3 2010), then calculate the modulo 7 + 1 of the number of days since that Sunday.
The problem is that including a literal date in the function call is enough to mark it as non-deterministic. You can work around that by using the integer 0 to represent the epoch, which for SQL Server is Jan 1st, 1900, a Sunday.
datediff(dd,0,[DateColumn]) % 7 + 1
The +1 just makes the result work the same as datepart(dw,[datecolumn]) when datefirst is set to 7 (default for US), which sets Sunday to 1, Monday to 2, etc
I can also use this in conjunction with case [thatComputedColumn] when 1 then 'Sunday' when 2 then 'Monday' ... etc. Wordier, but deterministic, which was a requirement in my environs.
Taken from Deterministic scalar function to get week of year for a date
;
with
Dates(DateValue) as
(
select cast('2000-01-01' as date)
union all
select dateadd(day, 1, DateValue) from Dates where DateValue < '2050-01-01'
)
select
year(DateValue) * 10000 + month(DateValue) * 100 + day(DateValue) as DateKey, DateValue,
datediff(day, dateadd(week, datediff(week, 0, DateValue), 0), DateValue) + 2 as DayOfWeek,
datediff(week, dateadd(month, datediff(month, 0, DateValue), 0), DateValue) + 1 as WeekOfMonth,
datediff(week, dateadd(year, datediff(year, 0, DateValue), 0), DateValue) + 1 as WeekOfYear
from Dates option (maxrecursion 0)
There is an already built-in function in sql to do it:
SELECT DATEPART(weekday, '2009-11-11')
EDIT:
If you really need deterministic UDF:
CREATE FUNCTION DayOfWeek(#myDate DATETIME )
RETURNS int
AS
BEGIN
RETURN DATEPART(weekday, #myDate)
END
GO
SELECT dbo.DayOfWeek('2009-11-11')
EDIT again: this is actually wrong, as DATEPART(weekday) is not deterministic.
UPDATE:
DATEPART(weekday) is non-deterministic because it relies on DATEFIRST (source).
You can change it with SET DATEFIRST but you can't call it inside a stored function.
I think the next step is to make your own implementation, using your preferred DATEFIRST inside it (and not considering it at all, using for example Monday as first day).
The proposed solution has one problem - it returns 0 for Saturdays. Assuming that we're looking for something compatible with DATEPART(WEEKDAY) this is an issue.
Nothing a simple CASE statement won't fix, though.
Make a function, and have #dbdate varchar(8) as your input variable.
Have it return the following:
RETURN (DATEDIFF(dd, -1, convert(datetime, #dbdate, 112)) % 7)+1;
The value 112 is the sql style YYYYMMDD.
This is deterministic because the datediff does not receive a string input, if it were to receive a string it would no longer work because it internally converts it to a datetime object. Which is not deterministic.
Not sure what you are looking for, but if this is part of a website, try this php function from http://php.net/manual/en/function.date.php
function weekday($fyear, $fmonth, $fday) //0 is monday
{
return (((mktime ( 0, 0, 0, $fmonth, $fday, $fyear) - mktime ( 0, 0, 0, 7, 17, 2006))/(60*60*24))+700000) % 7;
}
The day of the week? Why don't you just use DATEPART?
DATEPART(weekday, YEAR_DATE)
Can't you just select it with something like:
SELECT DATENAME(dw, GETDATE());

Resources