5

I need to display a number of days to upcoming payment day (let's say it is always at 10th of any month).

How do I do that in bash ?

4

dom = day of month

dom=6 ; \
days=$[ ${dom}-$(date +%-d) ] ; \
[ ${days} -lt 0 ] && days=$[ ${days} + $(date +%d -d "$(date +%Y%m01 -d 'next month') yesterday") ] ; \
echo ${days} days

30 days
  • This is wrong when day of month is greater than 10. – kmkaplan Mar 7 '17 at 20:38
  • Anyway to adjust for such case? – Michal Przybylowicz Mar 7 '17 at 20:41
  • See my updated answer (I think this is right). If the first result we get is negative, need to calculate number of days in current month and add that to the result. Also, I believe adding 1 is an error as, if pay day was today, the result should be 0. – parkamark Mar 7 '17 at 21:23
  • 1
    That also assumes GNU date and that $IFS doesn't contain digits. – Stéphane Chazelas Mar 7 '17 at 21:33
  • Further updated to not use Unix timestamps with the "86400 seconds in a day". – parkamark Mar 7 '17 at 21:58
11

While bash now has date formatting capabilities, it has no date parsing or calculation ones, so you may want to use another shell like ksh93 or zsh or a proper programming language like perl or python.

With ksh93, the difficult part is to find out what date format are supported as it's hardly documented (you can always have a look at the test data though for examples).

For instance, it does support crontab-like time specification and then gives you the next time that matches the specification, so you can do:

now=$(printf '%(%s)T')
next_10th=$(printf '%(%s)T' '* * 10 * *')

echo "Pay day is in $(((next_10th - now) / 86400)) days"

Now with standard utilities, it's not so difficult to implement:

eval "$(date '+day=%d month=%m year=%Y')"
day=${day#0} month=${month#0}
if [ "$day" -le 10 ]; then
  delta=$((10 - day))
else
  case $month in
    [13578]|10|12) D=31;;
    2) D=$((28 + (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0))));;
    *) D=30;;
  esac
  delta=$((D - day + 10))
fi
echo "Pay day is in $delta days"
  • The value of the selected pay day (which is 10 in this example) would generate wrong results if it is chosen to be 30 (for February) or 31 (for 4 of the months). – sorontar Mar 9 '17 at 21:50
  • @sorontar, I don't think I'd be willing to work for a company that pays me only on those months that have 31 days :-) – Stéphane Chazelas Mar 9 '17 at 22:30
  • That will be no problem at all if the company will default to the last day of the month if the selected PayDay is bigger. That is: use 28 in non-leap years if the selected PayDay is 29, 30, or 31. Which, come to think of it, is what a PayDay of 1 does: the next day after the end of the month. – sorontar Mar 9 '17 at 23:18
0
echo $(expr '(' $(date -d 2017/03/10 +%s) - $(date +%s) + 86399 ')' / 86400) "days for my payment"
3 days for my payment
  • 1
    This will fail in 3 days. – kmkaplan Mar 7 '17 at 20:40
  • @kmkaplan it's just an example to be adapted...For example passing the month value manually or attributing it to a variable... – Eduardo Baitello Mar 7 '17 at 20:44
0

We just need to substract the present day (today day $td) from the expected PayDay selected.

If the PayDay is bigger than the present day, the result will be positive and correct.

For example td=8 and pd=15:

$ td=8; pd=15
$ echo "The next PayDay will be in $((pd-td)) days"
7

If the result is negative, we just add the number of days of the present month.

The script to perform that may be:

#!/bin/bash

pd=${1:-10}               # Pay day selected
td=$( date -u +'%-d' )    # Today day of the month.

# To calculate the number of days in the present month.
MonthDays=$(  date +'%-d' -ud "$(date +"%Y-%m-01T00:00:00UTC") next month last day"  )
# Maybe a simpler alternative for current month last day:
# echo $(cal) | awk '{print $NF}'     # $(cal) is unquoted on purpose.

# Make the next PayDay fit within the available days in the month.
# If the selected PayDay given to the script is 31 and the month
# only has 30 days, the next PayDay should be 30,
# not an un-existent and impossible 31.
pd=$(( (pd>MonthDays)?MonthDays:pd ))

res=$(( pd-td ))
# If the value of res is negative, just add the number of days in present month.
echo "Pay Day is in $(( res+=(res<0)?MonthDays:0 )) days"    

Note that the unique date command need used only the present month, so, no month/year boundaries were crossed. That avoids almost all problems. The only assumption is that the present month starts at day 01. Also, the calculation is done at UTC+0 which avoids possible problems with DST (daylight saving time) or local changes.

If the PayDay selected (Say 31) is bigger than the number of days possible in the month (Say February with 28), the program assumes such 28 is the PayDay instead of an in-existent (for February) 31.

Calling the script (if today is day 9) :

$ ./script 7
Pay Day is in 29 days

$ ./script 16
Pay Day is in 7 days

$ ./script 31
Pay Day is in 19 days

But if today is day 28 of February:

$ ./script 8
Pay Day is in 8 days

$ ./script 28
Pay Day is in 0 days

$ ./script 31
Pay Day is in 0 days

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.