Home > Software design >  How to get the full calendar months between two dates in PostgreSQL
How to get the full calendar months between two dates in PostgreSQL

Time:12-20

I have two dates, e.g. '2022-01-03' and '2022-03-04', is there any neat way to calculate ONLY the completed full calendar months between these dates?

Some examples with their requested outputs:

'2022-01-03' and '2022-03-04'

full calendar months = 1 since only February was a full calendar month between this timespan.

'2022-01-01' and '2022-05-30'

full calendar months = 4 since May has 31 days total.

'2022-01-31' and '2022-05-31'

full calendar months = 3 since the month of May is not completed.

I tried subtracting the dates but it gives me the days difference between these dates. I also tried the function AGE() but it is based also in the days difference, since it is using days to calculate years months etc.

CodePudding user response:

First adjust the two dates to be the first day of the first full month and the last day of the last full month plus one day (t CTE) and then calculate the age between the two adjusted dates. Here is an illustration using sample_data CTE.

with sample_data(d1, d2) as (values
('2022-01-03'::date,'2022-03-04'::date),('2022-01-01','2022-05-30'),('2022-01-31','2022-05-31')),

t as
( 
 select 
  case when d1 = date_trunc('month', d1) 
       then d1 
       else date_trunc('month', d1)   interval '1 month' end::date m1,
  case when d2 = date_trunc('month', d2)   interval '1 month - 1 day' 
       then d2   interval '1 day'
       else date_trunc('month', d2) end::date m2
 from sample_data
) 
select m1, m2, age(m2, m1) from t;
m1 m2 age
2022-02-01 2022-03-01 1 mon
2022-01-01 2022-05-01 4 mons
2022-02-01 2022-06-01 4 mons

CodePudding user response:

You can generate a full range of days between the two dates as complete months, and then left join all the days only between the original dates. This way, you can check if any months are missing days. The query below is formatted as a function for ease of use:

create function calendar_months(a timestamp, b timestamp) 
returns int as
$$
declare
 total int;
begin
  select sum(case when t1.rec = t1.total then 1 else 0 end) into total 
  from (select extract(year from t), extract(month from t), 
    sum(case when t1 is not null then 1 else 0 end) rec, count(*) total 
    from generate_series(date_trunc('month', a), date_trunc('month', b)   interval '1 month' - interval '1 day', interval '1 day') t 
    left join generate_series(a, b - interval '1 day', interval '1 day') t1 on t = t1 
    group by extract(year from t), extract(month from t)) t1;
  return total;
end;
$$ language plpgsql;

See fiddle.

  • Related