How to add number of business days to given date

后端 未结 3 1858
花落未央
花落未央 2020-12-19 21:26

I\'m looking for a function which adds number of business days to given date.

Holidays table

create table pyha (pyha date primary key) ;
insert into          


        
3条回答
  •  情歌与酒
    2020-12-19 22:02

    I've had this issue myself - here is the function i have written to replace the excel workday() function as closely as possible, allowing negative workday additions as well as positive

    create or replace function workday(startdate date, i integer) returns date as 
    $$ 
    with workday_cte as (
        select s.a::date as workday from 
        generate_series(startdate - ((abs(i) * 2 + 10) || ' day')::interval, 
        startdate + ((abs(i) * 2 + 10) || ' day')::interval, '1 day'::interval) s(a)
        where extract(dow from s.a) between 1 and 5
        except 
        select holiday
        from holidays
    )
    select case when i > 0 then a.workday when i = 0 then startdate else b.workday end from 
    (
       select * from workday_cte where workday > startdate 
       order by workday asc limit 1 offset greatest(abs(i) - 1,0)
    ) as a, 
    (
       select * from workday_cte where workday < startdate 
       order by workday desc limit 1 offset greatest(abs(i) - 1,0)
    ) as b
    $$ language sql;
    

    You still need to update the section select holiday from holidays to your own table of holiday dates.

    Here also is a networkdays() function replacement, where similarly you need to update the holidays table - but note that if startdate is not before enddate it returns 0 unlike the excel function

    create or replace function networkdays(startdate date, enddate date) returns bigint as
    $$
    with workday_cte as (
        select s.a::date as workday from 
        generate_series(startdate, enddate, '1 day'::interval) s(a)
        where extract(dow from s.a) between 1 and 5
        except 
        select holiday
        from data.dtdholidays
    )
    select count(workday_cte.workday) from workday_cte
    $$ language sql;
    

    running this query to check the outputs:

    select *, networkdays("wd-1", wd1) from 
    (
       select day, workday(day, 1) as wd1, workday(day,0) as wd0, workday(day,-1) as "wd-1" 
       from (select day::date 
             from generate_series('2019-12-16'::date, '2019-12-23'::date, '1 day'::interval
             ) days(day)
    ) days(day)) a;
    

    gives me:

        day     |    wd1     |    wd0     |    wd-1    | networkdays
    ------------+------------+------------+------------+-------------
     2019-12-16 | 2019-12-17 | 2019-12-16 | 2019-12-13 |           3
     2019-12-17 | 2019-12-18 | 2019-12-17 | 2019-12-16 |           3
     2019-12-18 | 2019-12-19 | 2019-12-18 | 2019-12-17 |           3
     2019-12-19 | 2019-12-20 | 2019-12-19 | 2019-12-18 |           3
     2019-12-20 | 2019-12-23 | 2019-12-20 | 2019-12-19 |           3
     2019-12-21 | 2019-12-23 | 2019-12-21 | 2019-12-20 |           2
     2019-12-22 | 2019-12-23 | 2019-12-22 | 2019-12-20 |           2
     2019-12-23 | 2019-12-24 | 2019-12-23 | 2019-12-20 |           3
    

    Alternate weekend/holiday handling

    In some situations it is better that the number of workdays in the period created by adding or subtracting a fixed number of days is constant - for example in the table above when a weekend date (the 21st or 22nd of december) is used as the startdate then networkdays is 2 and not 3. You can change the behaviour of the workday function so that the networkdays is constant by rolling to the next workday before adding or subtracting the workdays required

    create or replace function workdaycwd(startdate date, i integer) returns date as 
    $$ 
    with workday_cte as (
        select s.a::date as workday from 
        generate_series(startdate - ((abs(i) * 2 + 10) || ' day')::interval, 
        startdate + ((abs(i) * 2 + 10) || ' day')::interval, '1 day'::interval) s(a)
        where extract(dow from s.a) between 1 and 5
        except 
        select holiday
        from data.dtdholidays
    )
    select case when i >= 0 then a.workday else b.workday end from 
    (
       select * from workday_cte where workday >= startdate 
       order by workday asc limit 1 offset (abs(i))
    ) as a, 
    (
       select * from workday_cte where workday < startdate 
       order by workday desc limit 1 offset greatest(abs(i) - 1, 0)
    ) as b
    $$ language sql;
    

    running a similar query as above gives

        day     |    wd1     |    wd0     |    wd-1    | networkdays
    ------------+------------+------------+------------+-------------
     2019-12-16 | 2019-12-17 | 2019-12-16 | 2019-12-13 |           3
     2019-12-17 | 2019-12-18 | 2019-12-17 | 2019-12-16 |           3
     2019-12-18 | 2019-12-19 | 2019-12-18 | 2019-12-17 |           3
     2019-12-19 | 2019-12-20 | 2019-12-19 | 2019-12-18 |           3
     2019-12-20 | 2019-12-23 | 2019-12-20 | 2019-12-19 |           3
     2019-12-21 | 2019-12-24 | 2019-12-23 | 2019-12-20 |           3
     2019-12-22 | 2019-12-24 | 2019-12-23 | 2019-12-20 |           3
     2019-12-23 | 2019-12-24 | 2019-12-23 | 2019-12-20 |           3
    

提交回复
热议问题