Reputation: 1
Many suggest how to get first date of the current year (month based) for instance:
SELECT DATEADD(yy, DATEDIFF(yy, 0, GETDATE()), 0)
this will result in:
2021-01-01 00:00:00.000
It should be 2021-01-04. (monday (sunday for US) of the first week)
What I need is similar but week based. The first week may start on 31 of December for instance, so the function should return 31 December, not 1 January.
Any ideas ?
Upvotes: 0
Views: 1024
Reputation:
Much better to use an inline table-valued function for this than a scalar function (some details over here).
I created one that generates the first Monday for the year of any given date, based on the desired results shown in the comment above. If you want something for Sunday, provide sample data / desired results in your question:
CREATE FUNCTION dbo.GetFirstMonday
(
@date date
)
RETURNS TABLE
WITH SCHEMABINDING
AS
RETURN
(
WITH km(km) AS (SELECT CONVERT(date, '20180101')), -- known Monday
d AS (SELECT FirstOfYear = DATEFROMPARTS(YEAR(@date),1,1)),
w AS (SELECT FirstMonday = DATEADD(WEEK, DATEDIFF(WEEK, km, FirstOfYear)
+ CASE WHEN ((@@DATEFIRST + DATEPART(WEEKDAY, FirstOfYear))) IN (6,13)
THEN 1 ELSE 0 END, km) FROM d CROSS JOIN km)
SELECT FirstMonday FROM w
);
Usage:
SET DATEFIRST 5;
DECLARE @src TABLE(InputDate date);
INSERT @src(InputDate) VALUES
('20160608'),('20170505'),('20180405'),
('20190303'),('20200903'),('20210706');
SELECT * FROM @src AS src
CROSS APPLY dbo.GetFirstMonday(src.InputDate);
Results (for any DATEFIRST
setting):
InputDate FirstMonday
---------- -----------
2016-06-08 2016-01-04
2017-05-05 2017-01-02
2018-04-05 2018-01-01
2019-03-03 2018-12-31
2020-09-03 2019-12-30
2021-07-06 2021-01-04
Another way to think about it is: if the first Monday of the year is more than 2 days from the first day of the year (e.g. the year starts on , then take the first Monday before the first of the year, which can be at most 3 days prior. So this function takes the MIN()
of the matching weekday from 7-day range from {first of year} - {3 days}
=> {first of year} + {3 days}
- one of those days has to be the Monday you're after (and if your rule isn't three days, it's easy to shift):
CREATE FUNCTION dbo.GetFirstWeekday
(
@date date,
@DayName char(6)
)
RETURNS TABLE WITH SCHEMABINDING
AS
RETURN (WITH n(n) AS (SELECT -3 UNION ALL SELECT n+1 FROM n WHERE n < 3),
d(d) AS (SELECT DATEADD(DAY, n, DATEFROMPARTS(YEAR(@date),1,1)) FROM n)
SELECT DayName = @DayName, FirstWeekday = MIN(d) FROM d
WHERE DATENAME(WEEKDAY, d) = @DayName
);
So given:
DECLARE @src TABLE(InputDate date);
INSERT @src(InputDate) VALUES
('20160108'),('20170505'),('20180405'),
('20190303'),('20200403'),('20210506');
SELECT * FROM @src AS src
CROSS APPLY dbo.GetFirstWeekday(src.InputDate, 'Monday');
Results:
InputDate FirstWeekday
---------- ------------
2016-06-08 2016-01-04
2017-05-05 2017-01-02
2018-04-05 2018-01-01
2019-03-03 2018-12-31
2020-09-03 2019-12-30
2021-07-06 2021-01-04
This also doesn't have any ties to @@DATEFIRST
but it does have a dependency on @@LANGUAGE
being in the English realm.
But really, if it's just one Sunday or Monday for each of a handful of years and you already know the rules, why not just create a table, define those rules once, instead of coming up with wacky and overtly flexible syntax?
CREATE TABLE dbo.FirstSundayMondayRules
(
TheYear int PRIMARY KEY,
FirstSunday date NOT NULL,
FirstMonday date NOT NULL
);
-- guessing at your Sunday rules here
INSERT dbo.FirstSundayMondayRules VALUES
(2016, '20160103', '20160104'),
(2019, '20181230', '20181231');
Now you can create a much simpler function:
CREATE FUNCTION dbo.GetFirstWeekday
(
@Date date,
@DayName char(6)
)
RETURNS TABLE WITH SCHEMABINDING
AS
RETURN (SELECT FirstWeekday = CASE @DayName
WHEN 'Sunday' THEN FirstSunday
WHEN 'Monday' THEN FirstMonday END
FROM dbo.FirstSundayMondayRules
WHERE TheYear = DATEPART(YEAR, @Date)
);
And DATEFIRST
has no relevance whatsoever anymore, at least as part of any calculation:
SET DATEFIRST 5;
DECLARE @src TABLE(InputDate date);
INSERT @src(InputDate) VALUES
('20160608'),('20190303');
SELECT 'Sunday', * FROM @src AS src
CROSS APPLY dbo.GetFirstWeekday(src.InputDate, 'Sunday');
SELECT 'Monday', * FROM @src AS src
CROSS APPLY dbo.GetFirstWeekday(src.InputDate, 'Monday');
Results:
(No column name) InputDate FirstWeekday
---------------- ---------- ------------
Sunday 2016-06-08 2016-01-03
Sunday 2019-03-03 2018-12-30
(No column name) InputDate FirstWeekday
---------------- ---------- ------------
Monday 2016-06-08 2016-01-04
Monday 2019-03-03 2018-12-31
Or better yet, get yourself a Calendar table, and you can just have columns where you hard-code FirstSundayOfYear
and FirstMondayOfYear
.
Upvotes: 1
Reputation: 1
so i figured out a solution , but may be there is a more elegant one
-- =============================================
-- Author: PLANSIS
-- Create date: 2020-11-25
-- Description: first day of the current year week based (monday of the first week)
-- =============================================
CREATE OR ALTER FUNCTION [dbo].[A_FN_TI_FirstDayCurrentYearWeek]
(
@date date = null
)
RETURNS datetime
AS
BEGIN
set @date = coalesce( @date , getdate() )
DECLARE @ResultVar datetime
DECLARE @wk int
DECLARE @yr int
SET @yr = year(@date)
SET @wk = 1
SELECT @ResultVar = dateadd (week, @wk, dateadd (year, @yr-1900, 0)) - 4 -
datepart(dw, dateadd (week, @wk, dateadd (year, @yr-1900, 0)) - 4) + 2 -- +2 for europe, +1 for US
-- Return the result of the function
RETURN @ResultVar
END
Upvotes: 0