Daniel
Daniel

Reputation: 95

Decrease a date correctly by each month

I have a simple code, it is working but not as intended. The main propose of my code is to decrease the given date until the target date 2012-01-31,

But the results:

2017-10-28
2017-09-28
2017-08-28
2017-07-28
2017-06-28
2017-05-28
2017-04-28
2017-03-28
2017-02-28
2017-01-28
2016-12-28
2016-11-28
2016-10-28
2016-09-28
2016-08-28
2016-07-28
2016-06-28
2016-05-28
2016-04-28
2016-03-28
2016-02-28
2016-01-28
...

The code:

#!/bin/bash

d=2017-10-31
while [ "$d" != 2012-01-31 ]; do 
  echo $d
  d=$(date -I -d "$d -1 month")
done

I really wish for the correct months in-placed like:

2017-10-31
2017-09-30
2017-08-31
2017-07-31
2017-06-30
2017-05-31
2017-04-30
2017-03-31
2017-02-28
2017-01-31
2016-12-31
2016-11-30
2016-10-31
2016-09-30
2016-08-31
2016-07-31
2016-06-30
2016-05-31
2016-04-30
2016-03-31
2016-02-29
2016-01-31
...

How can I manage to do this?

Upvotes: 0

Views: 88

Answers (4)

floydn
floydn

Reputation: 1131

Another option like randomir's answer is to use the relative word yesterday instead of day ago.

d=2017-10-31
while [ "$d" != 2012-01-31 ]; do 
    echo $d
    d=$(date -I -d "${d%-*}-01 yesterday")
done

Upvotes: 0

randomir
randomir

Reputation: 18697

Assuming your starting date is always the last day in month, you can let date take care of counting days per month if you use this algorithm:

date = date +1 day -1 month -1 day

Namely, first flip the date to the 1st day of next month, then simply subtract one month to get the 1st day of current month, and finally just subtract 1 day to get the last day of the previous month.

For example:

#!/bin/bash
d=2017-10-31
while [ "$d" != 2012-01-31 ]; do 
    echo $d
    d=$(date -I -d "$(date -I -d "$(date -I -d "$d +1 day") 1 month ago") 1 day ago")
done

Note 1 day ago is used instead of -1 day, because the latter can be interpreted as a timezone by date (known gotcha).


Alternatively, you can manually set the day to 01 (using shell's parameter expansion for suffix removal ${d%-*}) and then simply subtract 1 day with date:

d=2017-10-31
while [ "$d" != 2012-01-31 ]; do 
    echo $d
    d=$(date -I -d "${d%-*}-01 1 day ago")
done

The output is (in both cases):

2017-10-31
2017-09-30
2017-08-31
2017-07-31
2017-06-30
2017-05-31
2017-04-30
...

Upvotes: 1

karakfa
karakfa

Reputation: 67527

It's easier to generate the beginning of the month and do little trick to get the end of month without checking dates

$ d=2017-10-31; bd=$(date -I -d "$d +1 month");   
  while [ $d != 2012-01-31 ];        
  do 
      bd=$(date -I -d "$bd - 1 month");           
      d=$(date -I -d "$bd - 1 day");  
      echo $d;
  done

2017-10-31
2017-09-30
2017-08-31
2017-07-31
...  
2016-07-31
2016-06-30
2016-05-31
2016-04-30
2012-03-31
2012-02-29
2012-01-31

Upvotes: 1

JNevill
JNevill

Reputation: 50219

Perhaps instead of subtracting a month (which is notorious for this behavior) you can subtract the number of days that are in the current month

!/bin/bash

d=2017-10-31
while [ "$d" != 2012-01-31 ]; do
  echo $d
  days_in_month==$(cal $(date +"%m %Y" -d $d) | awk 'NF {DAYS = $NF}; END {print DAYS}')
  d=$(date -I -d "$d $days_in_month day ago")
done

Upvotes: 1

Related Questions