0

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 ?

maximnl
  • 1
  • 2
  • 2
    What defines the "first week"? For example, in [ISO terms](https://en.wikipedia.org/wiki/ISO_week_date), the first week of a year isn't until the week has a Thursday/contains January 4th. – Thom A Sep 17 '21 at 15:48
  • 2
    A much better and more intuitive (but not necessarily faster by any measurable amount) way to get the first date of the current year is `SELECT DATEFROMPARTS(YEAR(GETDATE()), 1, 1);` It might make more sense for you to have a [calendar table](https://www.mssqltips.com/sqlservertip/4054/creating-a-date-dimension-or-calendar-table-in-sql-server/) where you can explicitly define the first day of the first week of the year the way you want to, according to your rules, regardless of what SQL Server's `WEEK` and `ISO_WEEK` date parts say. – Aaron Bertrand Sep 17 '21 at 15:48
  • Can you provide a few years of examples (say, 5 years back through 5 years forward), where you can explicitly tell us what you expect the start of the "first week" for each of those years? – Aaron Bertrand Sep 17 '21 at 16:12
  • i really need this for just the current year and wrapped in a scalar function. the issue with functions - no support for FIRSTDAY options , but also i cannot set default values for the parameters (only stored procedures allow that) , so i do not wanna change all references in case i add a parameter for years (to get dates for other years ). the brutal way is to check if the dayname is sunday – maximnl Sep 20 '21 at 12:09
  • isoyear (No column name) 2016 2016-01-04 2017 2017-01-02 2018 2018-01-01 2019 2018-12-31 2020 2019-12-30 2021 2021-01-04 – maximnl Sep 20 '21 at 12:42
  • The point is we need to understand how you want to handle it for different years so we can give you a solution that doesn't stop working in a few months. – Aaron Bertrand Sep 20 '21 at 21:34

2 Answers2

1

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.

  • Example db<>fiddle - Sunday's there too, but I was guessing at your rules since you didn't supply any sample data / desired results.

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.

Aaron Bertrand
  • 272,866
  • 37
  • 466
  • 490
0

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
maximnl
  • 1
  • 2