1

We have WeekNo and Year entered into a table in following format:

YYYY-ww     (2021-48)

I want start date and end date from given week no with year as above format.

How to get it from SQL query or procedure?

Thanks,

LukStorms
  • 28,916
  • 5
  • 31
  • 45
Bhavin
  • 21
  • 1
  • 7

3 Answers3

2

How about this?

declare @dateText varchar(7);
declare @week int;
declare @year int;

set datefirst 1;              -- Set first day of week to Monday
set @dateText = '2021-48';
set @year = SUBSTRING(@dateText, 1, 4)
set @week = SUBSTRING(@dateText, 6, 2)

select DATEADD(week, @week, DATEADD(year, @year-1900, 0)) - 4 -
     DATEPART(dw, DATEADD(week, @week, DATEADD(year, @year-1900, 0)) - 4) + 1

Output
-----------------
2021-11-29 00:00:00.000

Alternatively you could create a function:

CREATE FUNCTION dbo.GetStartOfWeek(@dateText varchar(7))
RETURNS Datetime
BEGIN
    
declare @week int;
declare @year int;
declare @offset int;

select
    @offset =  @@DATEFIRST - 1, 
    @year = SUBSTRING(@dateText, 1, 4),
    @week = SUBSTRING(@dateText, 6, 2)

 RETURN DATEADD(week, @week, DATEADD(year, @year-1900, 0)) - 4 -
        DATEPART(dw, DATEADD(week, @week, DATEADD(year, @year-1900, 0) + @offset) - 4) + 1
END;

Which you can use in a SQL query or stored procedure:

select dbo.GetStartOfWeek('2021-48') as StartDate, DATEADD(d, 7, dbo.GetStartOfWeek('2021-48')) as EndDate
openshac
  • 4,966
  • 5
  • 46
  • 77
  • ..and what about end date of that week??? – Bhavin Nov 30 '21 at 09:54
  • You can just do something similar to the above, either a separate function or by adding 7 days to the start of the week. – openshac Nov 30 '21 at 10:02
  • Added the end date (7 days after the start date), you can change it 6 if you want the beginning of the end date depending on your requirements. Does that work for you now @Bhavin ? – openshac Nov 30 '21 at 14:03
  • Yes, developed 2 functions for start date and end date. but as we know that in functions, we cannot use set datefirst 1. so need a solution for this... – Bhavin Dec 01 '21 at 03:37
  • Why don't you just use an offset for the first day of the week like above then? – openshac Dec 01 '21 at 11:00
2

(An extra answer, since this method is a bit different.)

This method is about using a tally table with dates and date related stuff.

Then such calendar table can be used to lookup the year and week.

For example

Create calendar table

create table REF_Calendar (
 cal_date date primary key not null,
 cal_year smallint not null,
 cal_month tinyint not null,
 cal_day tinyint not null,
 cal_dayofyear smallint not null,
 cal_quarter tinyint not null,
 cal_weekday_abbrev char(3) not null,
 cal_iso_week tinyint,
 cal_df7_week tinyint not null,
 cal_df7_weekday tinyint not null,
 cal_df1_week tinyint,
 cal_df1_weekday tinyint,
 cal_mooncycle tinyint,
 index idx_Calendar_year_week unique (cal_year, cal_df7_week, cal_df7_weekday)
);

Fill the calendar

SET DATEFIRST 7; -- 7: week starts on sunday

declare @startdate date = '2020-01-01';
declare @enddate   date = '2023-01-01';

with rcte_calendar as (
  select @startdate as cal_date
  union all
  select dateadd(day, 1, cal_date)
  from rcte_calendar
  where cal_date < @enddate
)
insert into ref_calendar 
(cal_date, cal_year, cal_month, cal_day, cal_dayofyear, cal_quarter, cal_weekday_abbrev, 
 cal_df7_week, cal_df7_weekday, cal_mooncycle)
select
  cal_date
, datepart(year, cal_date) as cal_year
, datepart(month, cal_date) as cal_month
, datepart(day, cal_date) as cal_day
, datepart(dayofyear, cal_date) as cal_dayofyear
, datepart(quarter, cal_date) as cal_quarter
, lower(left(datename(weekday, cal_date), 3)) as cal_weekday_abbrev
, datepart(week, cal_date) as cal_df7_week
, datepart(weekday, cal_date) as cal_df7_weekday
, abs(1-abs(round(((14-1.0*CONVERT(int, CONVERT(nvarchar(2), cal_date, 131)))/14)
 , 1, 1)))*100 as cal_mooncycle
from rcte_calendar cte
where not exists (
        select 1 
        from ref_calendar cal
        where cal.cal_date = cte.cal_date
      )
option (maxrecursion 0);

Updating extra's

SET DATEFIRST 1; -- 1: week starts on monday
update ref_calendar
set cal_df1_week = datepart(week, cal_date)
  , cal_df1_weekday = datepart(weekday, cal_date)
  , cal_iso_week = datepart(iso_week, cal_date)
where cal_df1_week is null
   or cal_df1_weekday is null
   or cal_iso_week is null;

Check how it looks

select top 9 * 
from ref_calendar 
where cal_mooncycle = 100
  and cal_date >= getdate()
  and cal_year <= 1 + datepart(year, current_timestamp)
order by cal_date asc;
cal_date cal_year cal_month cal_day cal_dayofyear cal_quarter cal_weekday_abbrev cal_iso_week cal_df7_week cal_df7_weekday cal_df1_week cal_df1_weekday cal_mooncycle
2021-12-17 2021 12 17 351 4 fri 50 51 6 51 5 100
2021-12-18 2021 12 18 352 4 sat 50 51 7 51 6 100
2021-12-19 2021 12 19 353 4 sun 50 52 1 51 7 100
2022-01-16 2022 1 16 16 1 sun 2 4 1 3 7 100
2022-01-17 2022 1 17 17 1 mon 3 4 2 4 1 100
2022-01-18 2022 1 18 18 1 tue 3 4 3 4 2 100
2022-02-14 2022 2 14 45 1 mon 7 8 2 8 1 100
2022-02-15 2022 2 15 46 1 tue 7 8 3 8 2 100
2022-02-16 2022 2 16 47 1 wed 7 8 4 8 3 100

Test applying the calendar to a table

DECLARE @Test TABLE (col char(7)); 
INSERT INTO @Test VALUES 
  ('2021-01'), ('2021-02')
, ('2021-48')
, ('2022-01'), ('2022-02')
;

select *
from @test t
outer apply (
  select 
    min(cal_date) as startofweek
  , max(cal_date) as endofweek
  from (values (try_cast(left(col, 4) as int),
            abs(try_cast(right(col, 2) as int))) 
  ) as val(yr, ww)
  join ref_calendar as cal
    on ((cal_year = yr and cal_df7_week = ww)
        or (ww = 1  and cal_year = yr-1 and cal_df7_week = 53) 
        or (ww = 53 and cal_year = yr+1 and cal_df7_week = 1))
) cal
col startofweek endofweek
2021-01 2020-12-27 2021-01-02
2021-02 2021-01-03 2021-01-09
2021-48 2021-11-21 2021-11-27
2022-01 2021-12-26 2022-01-01
2022-02 2022-01-02 2022-01-08

Demo on db<>fiddle here

LukStorms
  • 28,916
  • 5
  • 31
  • 45
1

Here are 2 ways to calculate the start & end day of a Year-Week string in a query.

1) Without UDF

SET DATEFIRST 7; -- 7: Sunday has weekday 1
DECLARE @Test TABLE ([YEAR-WW] char(7)); 
INSERT INTO @Test VALUES 
  ('2020-53'), ('2021-01'), ('2021-02')
, ('2021-48')
, ('2021-53'), ('2022-01');

SELECT [YEAR-WW]
, [FirstDayOfWeek] = CAST(DATEADD(day, 1-DATEPART(weekday, DATEADD(week, ABS(RIGHT([YEAR-WW],2))-1, LEFT([YEAR-WW],5)+'01-01')), DATEADD(week, ABS(RIGHT([YEAR-WW],2))-1, LEFT([YEAR-WW],5)+'01-01')) AS DATE)
, [LastDayOfWeek]  = CAST(DATEADD(day, 6, DATEADD(day, 1-DATEPART(weekday, DATEADD(week, ABS(RIGHT([YEAR-WW],2))-1, LEFT([YEAR-WW],5)+'01-01')), DATEADD(week, ABS(RIGHT([YEAR-WW],2))-1, LEFT([YEAR-WW],5)+'01-01'))) AS DATE)
FROM @Test
ORDER BY 1;
YEAR-WW FirstDayOfWeek LastDayOfWeek
2020-53 2020-12-27 2021-01-02
2021-01 2020-12-27 2021-01-02
2021-02 2021-01-03 2021-01-09
2021-48 2021-11-21 2021-11-27
2021-53 2021-12-26 2022-01-01
2022-01 2021-12-26 2022-01-01

2) Using this UDF

CREATE FUNCTION dbo.GetDateFromYearWeek (
 @YearWeek VARCHAR(7) = '000101', -- default
 @WeekDay INT = 1, -- default
 @FirstWeekDayName VARCHAR(9) = 'mon' -- default
) RETURNS DATE
BEGIN
 
 IF @YearWeek = '000101'
   SET @YearWeek = CONCAT(DATEPART(year, GETDATE()), '-', DATEPART(week, GETDATE())); 
 
 IF @YearWeek NOT LIKE '[0-9][0-9][0-9][0-9]%[0-9-][0-9]'
   RETURN NULL;
 
 IF @WeekDay < 1 OR @WeekDay > 7
   RETURN NULL;
 
 DECLARE @FirstWeekDay INT = CHARINDEX(LOWER(LEFT(@FirstWeekDayName,3)), '   montuewedthufrisatsun')/3;
 IF @FirstWeekDay = 0 -- not found in string
   SET @FirstWeekDay = @@DATEFIRST;
 
 DECLARE @Year INT = TRY_CAST(LEFT(@YearWeek, 4) AS INT);
 DECLARE @Week INT = ABS(TRY_CAST(RIGHT(@YearWeek, 2) AS INT));
 
 DECLARE @Date DATE = TRY_CAST(CONCAT(@Year,'-01-01') AS DATE);
 SET @Date = DATEADD(week, @Week-1, @Date);
 
 DECLARE @DowDiff INT = (6-@FirstWeekday+@@DATEFIRST+DATEPART(weekday,@Date))%7;
 SET @Date = DATEADD(day, -@DowDiff, @Date);
 SET @Date = DATEADD(day, @WeekDay-1, @Date);

 RETURN @Date;
END;

Example usage

DECLARE @Test TABLE ([YEAR-WW] char(7)); 
INSERT INTO @Test VALUES 
  ('2020-53'), ('2021-01'), ('2021-02')
, ('2021-48')
, ('2021-53'), ('2022-01'), ('2022-02')
;

SELECT [YEAR-WW]
, [FirstOfWeek_Mon] = dbo.GetDateFromYearWeek([YEAR-WW], 1, 'mon')
, [LastOfWeek_Mon]  = dbo.GetDateFromYearWeek([YEAR-WW], 7, 'Monday')
, [FirstOfWeek_Sun] = dbo.GetDateFromYearWeek([YEAR-WW], 1, 'sun')
, [LastOfWeek_Sun]  = dbo.GetDateFromYearWeek([YEAR-WW], 7, 'SUNDAY')
FROM @Test
ORDER BY 1;
YEAR-WW FirstOfWeek_Mon LastOfWeek_Mon FirstOfWeek_Sun LastOfWeek_Sun
2020-53 2020-12-28 2021-01-03 2020-12-27 2021-01-02
2021-01 2020-12-28 2021-01-03 2020-12-27 2021-01-02
2021-02 2021-01-04 2021-01-10 2021-01-03 2021-01-09
2021-48 2021-11-22 2021-11-28 2021-11-21 2021-11-27
2021-53 2021-12-27 2022-01-02 2021-12-26 2022-01-01
2022-01 2021-12-27 2022-01-02 2021-12-26 2022-01-01
2022-02 2022-01-03 2022-01-09 2022-01-02 2022-01-08

Demo on db<>fiddle here

LukStorms
  • 28,916
  • 5
  • 31
  • 45
  • I would also like to add to this answer that if you need this really often i would recommend using an explicit Calendar Table. Like in this answer: https://stackoverflow.com/questions/12597143/query-for-how-to-add-the-missing-dates-in-sql or hier the Second anwser: https://stackoverflow.com/questions/32474236/construct-date-from-year-and-week-number-in-mssql – MisterT Dec 03 '21 at 11:44
  • @MisterT I've played around with that and added an extra answer to this question. But you'll notice that week 1 & 53 are troublemakers for the lookup. – LukStorms Dec 03 '21 at 17:22
  • Yes you are right :-) I have never looked at it that way. (?!) For me i have a also a "Normal Week" in the Calendar Table. And i would probably only deliver for the First Week in 2021 - 3 Days....... In our Application starts the first Week in 2021 on 4.1. I have to keep that in mind. – MisterT Dec 06 '21 at 09:15
  • Side-note. An [ISO Week](https://en.m.wikipedia.org/wiki/ISO_week_date) version of the UDF can be found in [this answer](https://stackoverflow.com/a/70199863/4003419) – LukStorms Feb 24 '22 at 13:34