3

To find the number of days between two dates we can use something like this:

SELECT date_part('day',age('2017-01-31','2017-01-01')) as total_days;

In the above query we got 30 as output instead of 31. Why is that?
And I also want to find the number of days except Sundays. Expected output for the interval ('2017-01-01', '2017-01-31'):

Total Days = 31
Total Days except Sundays = 26
Erwin Brandstetter
  • 605,456
  • 145
  • 1,078
  • 1,228
Mani
  • 2,675
  • 2
  • 20
  • 42
  • 2
    Unrelated, but: `date_part('day',age('2017-01-31','2017-01-01'))` gives you a wrong result if the "age" is more then a month. e.g. `date_part('day', age('2016-02-28','2016-01-01'))` would return 27 although it should be 58. If you want the number of days between two dates, just subtract them: `date '2017-01-31' - date '2017-01-01'` –  Jan 26 '17 at 15:48
  • Thanks @a_horse_with_no_name... will use this – Mani Jan 26 '17 at 15:56

2 Answers2

10

You need to define "between two dates" more closely. Lower and upper bound included or excluded? A common definition would be to include the lower and exclude the upper bound of an interval. Plus, define the result as 0 when lower and upper bound are identical. This definition happens to coincide with date subtraction exactly.

SELECT date '2017-01-31' - date '2017-01-01' AS days_between

This exact definition is important for excluding Sundays. For the given definition an interval from Sun - Sun (1 week later) does not include the upper bound, so there is only 1 Sunday to subtract.

interval in days  | sundays
0                 | 0
1-6               | 0 or 1
7                 | 1
8-13              | 1 or 2
14                | 2
...

An interval of 7 days always includes exactly one Sunday.

We can get the minimum result with a plain integer division (days / 7), which truncates the result.

The extra Sunday for the remainder of 1 - 6 days depends on the first day of the interval. If it's a Sunday, bingo; if it's a Monday, too bad. Etc. We can derive a simple formula from this:

SELECT days, sundays, days - sundays AS days_without_sundays
FROM  (
   SELECT z - a AS days
      , ((z - a) + EXTRACT(isodow FROM a)::int - 1 ) / 7 AS sundays
   FROM  (SELECT date '2017-01-02' AS a       -- your interval here
               , date '2017-01-30' AS z) tbl
   ) sub;

Works for any given interval.
Note: isodow, not dow for EXTRACT().

To include the upper bound, just replace z - a with (z - a) + 1. (Would work without parentheses, due to operator precedence, but better be clear.)

Performance characteristic is O(1) (constant) as opposed to a conditional aggregate over a generated set with O(N).

Related:

Erwin Brandstetter
  • 605,456
  • 145
  • 1,078
  • 1,228
3

You could try using generate_series to generate all the dates between given date and then take count of required days.

SELECT
    count(case when extract(dow from generate_series) <> 0 then 1 end) n
from generate_series('2017-01-01'::date,'2017-01-31'::date, '1 day');
Gurwinder Singh
  • 38,557
  • 6
  • 51
  • 76