25

I have scheduled a Bash script to run on the 1st of the month but I need to create 2 variables in it with the 1st and last date of the previous month, whatever those may be.

Is it possible to do this using just Bash?

user4428391
  • 381
  • 1
  • 3
  • 8
  • Most (currently all) of the answers here require GNU `date` and thus basically assume Linux. There are near-duplicate questions which show how to do this with POSIX / BSD / Mac `date`. – tripleee Dec 23 '19 at 12:30
  • 1
    See e.g. https://stackoverflow.com/questions/13168463/using-date-command-to-get-previous-current-and-next-month which is tagged [tag:linux] but has several portable solutions to a related problem. – tripleee Dec 23 '19 at 12:36

7 Answers7

29

Unlike some answers, this will work for the 31st and any other day of the month. I use it to output unix timestamps but the output format is easily adjusted.

first=$(date --date="$(date +'%Y-%m-01') - 1 month" +%s)
last=$(date --date="$(date +'%Y-%m-01') - 1 second" +%s)

Example (today's date is Feb 14, 2019):

echo $first $last

1546300800 1548979199

To output in other formats, change final +%s to a different format such as +%Y-%m-%d or omit for default format in your locale.

In case you need, you can also back up an arbitrary number of months like this:

    # variable must be >= 1
    monthsago=23
    date --date="$(date +'%Y-%m-01') - ${monthsago} month"
    date --date="$(date +'%Y-%m-01') - $(( ${monthsago} - 1 )) month - 1 second"

Example output (today's date is Feb 15, 2019):

Wed Mar 1 00:00:00 UTC 2017
Fri Mar 31 23:59:59 UTC 2017

radio_tech
  • 671
  • 7
  • 5
15

You can try following date commands regardless of the day you are executing them to get first and last day of previous month

Firstday=`date -d "-1 month -$(($(date +%d)-1)) days"`
Lastday=`date -d "-$(date +%d) days"`
LogicIO
  • 627
  • 7
  • 15
9

Due to the varying length of months, I think the most dependable way to do this is to base the calendar offsets from the first day of the month rather than any other arbitrary date and then subtract the number of days...

In the snippet below, you can set $TODAY to whatever date you need and $LAST_MONTH_START and $LAST_MONTH_END will end up containing the previous month's start and end dates:

TODAY=$(date '+%F') # or whatever YYYY-MM-DD you need
THIS_MONTH_START=$(date -d "$TODAY" '+%Y-%m-01')
LAST_MONTH_START=$(date -d "$THIS_MONTH_START -1 month" '+%F')
LAST_MONTH_END=$(date -d "$LAST_MONTH_START +1 month -1 day" '+%F')
pirs
  • 2,410
  • 2
  • 18
  • 25
confirmator
  • 374
  • 3
  • 11
7

This can be done in two lines, tweak date format to suit.

START_LAST_MONTH=$(date "+%F" -d "$(date +'%Y-%m-01') -1 month")
END_LAST_MONTH=$(date "+%F" -d "$START_LAST_MONTH +1 month -1 day");

#Test Code
echo START_LAST_MONTH=$START_LAST_MONTH
echo END_LAST_MONTH=$END_LAST_MONTH

Running gives:

START_LAST_MONTH=2018-09-01
END_LAST_MONTH=2018-09-30

Boundary Testing

 for TEST_DATE in 2018-03-31 2018-12-31 2019-01-01
 do
      START_LAST_MONTH=$(date "+%F" -d "$(date -d $TEST_DATE +'%Y-%m-01') -1 month")
      END_LAST_MONTH=$(date "+%F" -d "$START_LAST_MONTH +1 month -1 day");
      echo TEST_DATE=$TEST_DATE, START_LAST_MONTH=$START_LAST_MONTH, END_LAST_MONTH=$END_LAST_MONTH
 done

Output

 TEST_DATE=2018-03-31, START_LAST_MONTH=2018-02-01, END_LAST_MONTH=2018-02-28
 TEST_DATE=2018-12-31, START_LAST_MONTH=2018-11-01, END_LAST_MONTH=2018-11-30
 TEST_DATE=2019-01-01, START_LAST_MONTH=2018-12-01, END_LAST_MONTH=2018-12-31
Anthony Palmer
  • 944
  • 10
  • 15
  • 1
    As much as I'd love this to work (I dislike the idea of nested date commands for such a mundane task), please note that this code will fail on the 31st of any month and on the last few days of March (depending on leap year). E.g. `date "+%Y-%m-01" -d "31 May 2019 -1 Month"` = `2019-05-01`. Fixing this answer will make it identical to all the other correct answers. – trs Dec 20 '19 at 08:10
  • Comment is out of date. – Anthony Palmer Dec 02 '21 at 14:20
2

Example:

#!/bin/bash
for monthsback in 1 2 3 4 5 6 7 8 9 10 11 12
do
    monthsfwd=`expr $monthsback - 1`
    startdate=`date -d "-${monthsback} month -$(($(date +%d)-1)) days" +%Y-%m-%d`
    enddate=`date -d "-$(date +%d) days -${monthsfwd} month" +%Y-%m-%d`
    echo "$monthsback month(s) ago:"
    echo $startdate
    echo $enddate
    echo ""
done

Will output the first and last day from (monthsback) months ago:

2016-06-01 - 2016-06-30

2016-05-01 - 2016-05-31

The following will set two variables with the start and end of the previous month:

#!/bin/bash
monthsback=1
monthsfwd=`expr $monthsback - 1`
startdate=`date -d "-${monthsback} month -$(($(date +%d)-1)) days" +%Y-%m-%d`
enddate=`date -d "-$(date +%d) days -${monthsfwd} month" +%Y-%m-%d`
ctoepfer
  • 21
  • 1
  • If you are on Bash, using `expr` for simple integer arithmetic is misguided. Use the built-in arithmetic like `monthsfwd=$((monthsback - 1))` – tripleee Dec 23 '19 at 12:33
2

TEST

first=`date -d "02/05/2018" +"%Y%m01"`
last=`date -d "02/05/2018 + 1 month-5 day" +"%Y%m%d"`

example

RUNDATE="20180207"
y="${RUNDATE:0:4}"
m="${RUNDATE:4:2}"
d="${RUNDATE:6:2}"
RUNDATE_START=`date -d "$m/$d/$y" +"%Y%m01"`
  RUNDATE_END=`date -d "$m/$d/$y + 1 month - $d day" +"%Y%m%d"`

result

20180201<br/>
20180228
Nam G VU
  • 33,193
  • 69
  • 233
  • 372
CHL
  • 21
  • 4
  • 1
    Welcome to StackOverflow. Answers with only code tend to get flagged as "Low Quality" and deleted. Please provide some basic explanation alongside your code. – Graham Feb 07 '18 at 03:50
0

If you're doing this on the 1st day of the month then you can use something like

first=$(date --date='-1 month')
last=$(date --date='-1 day')

But if you're running on another date then I guess you'll need to start from a known reference date.

owenrumney
  • 1,520
  • 3
  • 18
  • 37