It is behaving perfectly correctly.
This:
dateadd(month,
-6,
dateadd(day, -3, date('20180503'))
) AS "should be 31oct17"
is saying: "Give me the date which is 6 months before the date that is 3 days before 2018-05-03".
The date that is 3 days before 2018-05-03
is 2018-04-30
The date that is 6 months before 2018-04-30
is 2017-10-30
If, instead, you reversed the two operations:
- The date that is 6 months before
2018-05-03
is 2018-11-03
- The date that is 3 days before
2018-11-03
is 2018-10-31
(one day later)
So, the problem is caused by some months having 30 days and other months having 31 days.
It is possible that you are wanting "the last day of the month". This is always difficult and confusing for us humans and is best avoided. If you really need it, then it is easiest to obtain by subtracting a day from the first day of the next month:
SELECT
dt,
(dt + '1 month'::interval - '1 day'::interval)::date
FROM (SELECT ('2018-01-01'::date + '1 month'::interval * generate_series(0,11))::date as dt)
returns:
2018-01-01 2018-01-31
2018-02-01 2018-02-28
2018-03-01 2018-03-31
2018-04-01 2018-04-30
2018-05-01 2018-05-31
2018-06-01 2018-06-30
2018-07-01 2018-07-31
2018-08-01 2018-08-31
2018-09-01 2018-09-30
2018-10-01 2018-10-31
2018-11-01 2018-11-30
2018-12-01 2018-12-31