So what you are looking for is a hybrid ISO with standard Calendar. You are taking the ISO week starting and ending period, but instead of all weeks being exactly 7 days you potentially truncate the 1st and/or last weeks.
The change to need for this is not actually extensive. For initial query returns the in the ISO week begin date instead of the 1st of the month. Then the main query then checks for week 1 and if so produces the 1st of the month. The only twist is determining the ISO week begin date. For this I've just included a function I have had for some time specifically for that. The change to the week_days function are marked --<<<.
create or replace function iso_first_of_week(date_in date)
returns date
language sql
immutable strict
/*
Given a date return the 1st day of the week according to ISO-8601.
I.e. Return the Date if it is Monday otherwise return the preceding Monday
*/
AS $$
with wk_adj(l_days) as (values (array[0,1,2,3,4,5,6]))
select date_in - l_days[ extract (isodow from date_in)::integer ]
from wk_adj;
$$;
create or replace
function week_dates( do_date_in date)
returns table (week_num integer, first_date date, last_date date)
language sql
immutable strict
as $$
with recursive date_list(week_num,first_date,terminate_date) as
( select 1
, iso_first_of_week(do_date_in)::timestamp --<<<
, (date_trunc('month', do_date_in) + interval '1 month' - interval '1 day')::timestamp
union all
select week_num+1, (first_date+interval '7 day'), terminate_date
from date_list
where first_date+interval '6 day' < terminate_date::timestamp
)
select week_num
, case when week_num = 1 --<<<
then date_trunc('month', do_date_in)::date --<<<
else first_date::date --<<<
end --<<<
, case when (first_date+interval '6 day')::date > terminate_date
then terminate_date::date
else (first_date+interval '6 day')::date
end last_date
from date_list;
$$;
---------- Original Reply
You can use a recursive query CTE to get the week number and first date for each week of the month specified. The main query calculates the ending date, shorting the last if necessary. Then wrap that into a SQL function to return the week number and date range for each week. See example.
create or replace
function week_dates( do_date_in date)
returns table (ween_num integer, first_date date, last_date date)
language sql
immutable strict
as $$
with recursive date_list(week_num,first_date,terminate_date) as
( select 1
, date_trunc('month', do_date_in)::timestamp
, (date_trunc('month', do_date_in) + interval '1 month' - interval '1 day')::timestamp
union all
select week_num+1, (first_date+interval '7 day'), terminate_date
from date_list
where first_date+interval '6 day' < terminate_date::timestamp
)
select week_num
, first_date::date
, case when (first_date+interval '6 day')::date > terminate_date
then terminate_date::date
else (first_date+interval '6 day')::date
end last_date
from date_list;
$$;
Response to: "How can i put the output in a single row with week1, week2, week3, week4 and week5". This is essentially the initial output that did not satisfy what you wanted. The term for this type action is PIVOT and is generally understood. It stems from transforming row orientation to column orientation. It is not overly difficult but it is messy.
IMHO this is something that belongs in the presentation layer and is not suitable for SQL. After all you are rearranging the data structure for presentation purposes. Let the database server use its natural format, use the presentation layer to reformat. This allows reuse of the queries instead of rewriting when the presentation is changed or another view of the same data is required.
If you actually want this then just use your initial query, or see the answer from
@Bohemian. However the below shows how this issue can be handled with just SQL (assuming the function week_dates was created).
select week1s
, case when week5e is null
then week4e
else week5e
end "end of month"
, week1s || ' - ' || week1e
, week2s || ' - ' || week2e
, week3s || ' - ' || week3e
, week4s || ' - ' || week4e
, week5s || ' - ' || week5e
from ( select max(case when (week_num=1) then first_date else NULL end) as week1s
, max(case when (week_num=1) then last_date else NULL end) as week1e
, max(case when (week_num=2) then first_date else NULL end) as week2s
, max(case when (week_num=2) then last_date else NULL end) as week2e
, max(case when (week_num=3) then first_date else NULL end) as week3s
, max(case when (week_num=3) then last_date else NULL end) as week3e
, max(case when (week_num=4) then first_date else NULL end) as week4s
, max(case when (week_num=4) then last_date else NULL end) as week4e
, max(case when (week_num=5) then first_date else NULL end) as week5s
, max(case when (week_num=5) then last_date else NULL end) as week5e
from week_dates(current_date)
) w ;
As before I have wrapped the above in a SQL function and provide an example here.