Subtract months from a given date in bash

Viewed 2664

I want to subtract months from a given date.

businessDate='2019-12-31'
diff=1
busDate=$(date --date="$businessDate -$diff month" +%Y-%m-%d)
echo "Date: $busDate"

This code returns 2019-12-01 instead of 2019-11-30. Can someone please suggest a solution to get the correct date?

2 Answers

As indicate in 'info date', calculation of dates with relative month (1 month ago, etc.) is problematic, and sometime result in unexpected results:

From: 29.7 Relative items in date strings:

The fuzz in units can cause problems with relative items. For example, ‘2003-07-31 -1 month’ might evaluate to 2003-07-01, because 2003-06-31 is an invalid date

Some solutions:

  1. Treat '1 month ago' as a request to move days backward, where N is max(DD, days in previous month).
  2. Check if the result date has the same month as the original date, and move the date one day backward from the first day of the month. This is needed to handle March/Feb transition.
  3. Alternative approach is to perform the date movement on the first day of the month (which will always work), and then to cap the day part of the date, but the last date in the result month.

The benefit of the third approach is that it words for for month movement.

Implementation of option #3 is bash is a little bit cumbersome, probably better to write python/perl code.

IDATE=2019-12-31
N_MONTH=-1
DD_PART=$(date +'%d' -d "$IDATE")
YYYYMM=$(date +'%Y-%m' -d "$(date +'%Y-%m-01' -d "$IDATE") $N_MONTH month")
LAST_DAY_IN_YYYYMM=$(date +'%d' -d "$YYYYMM-01 +1month -1day")
if [[ "$LAST_DAY_IN_YYYYMM" -lt "$DD_PART" ]] ; then
    DD_PART=$LAST_DAY_IN_YYYYMM
fi
echo "$YYYYMM-$DD_PART"

First, this is actually the correct date! '2019-12-31' minus one months would be '2019-11-31' but as we have only 30 day in November, it maps to December, the 1st.

Still, if you want to get to the previous month for sure (I think it's namely what you want!), I may propose to add as many extra days as one needs for that. Please try this code:

businessDate='2019-12-3'
diff=1

thisMonth=$(date --date=$businessDate +%m)
extra=0 #extra days to reach the previous month for sure
while [ $(date --date="$businessDate -$diff month -$extra days" +%m) == $thisMonth ]
do
    extra=`expr $extra + 1`
done


busDate=$(date --date="$businessDate -$diff month -$extra days" +%Y-%m-%d)
echo "Date: $busDate"
Related