Sunday, September 4, 2022

When crontab schedule is not enough

As far as you know, cron provides the schedule on the following time attributes: minute, hour, day of the week, month, day of month. What if you need to set the specific schedule, for example, you need (not) to fire the event only on last working day of the current month and on the next day after it (2 days in a row) ? 

The application is restarting every day. Let's assume that working days between Monday and Friday, the current month (September) has 30 days, and last working day is Friday, 28th. So, we need (not) to fire the event on 28th and 29th. How to do this using only crontab file ? Have a look :

#
# If END_OF_MONTH=true, the app server is not stopped and not restarted automatically.
# END_OF_MONTH is set to true when current day is last working day of month, and next day after it (2 days in row).
# a and b - intermediate variables to keep END_OF_MONTH intermediate value.
# Exception when 30.04 is Monday (as in 2018 and 2029; then end of month must be moved to Saturday, 28.04, because 01.05 (day off) is Tuesday),
# or 29.04 is Radonica (in 2025 and 2036, so 30.04 is moved to 03.05, end of month in this case 28.04).
#
cy='date +%Y'
cm='LANG=en date +%b'
nm1wd='$(test $(eval $cm) = "Dec") && eval $(echo "LANG=en date +%u -d \"$(LANG=en date +%b -d "next month") 1 +1year\"") || eval $(echo "LANG=en date +%u -d \"$(LANG=en date +%b -d "next m
onth") 1\"")'
lcmd='LANG=en date +%d -d "$(LANG=en date +%b -d "next month") 1 -1day"'
cday='date +%-d'
cwday='date +%u'
fr1="cal -m | tail -n +3 | awk '{print $5}' | sed -n '4p'"
fr2="cal -m | tail -n +3 | awk '{print $5}' | sed -n '5p'"
last_fr='test -z "$(eval $fr2)" && echo $(eval $fr1) || echo $(eval $fr2)'
sa1="cal -m | tail -n +3 | awk '{print $6}' | sed -n '4p'"
sa2="cal -m | tail -n +3 | awk '{print $6}' | sed -n '5p'"
last_sa='test -z "$(eval $sa2)" && echo $(eval $sa1) || echo $(eval $sa2)'
_END_OF_MONTH='$(test $(eval $nm1wd) -ge 3 -a $(eval $nm1wd) -le 5 -a $(eval $cday) -eq $(eval $lcmd) || (test $(eval $nm1wd) -eq 6 -a $(eval $cday) -eq $(eval $last_fr)) || (test $(eval $n
m1wd) -eq 7 -o $(eval $nm1wd) -eq 1 && test $(eval $cday) -eq $(eval $last_fr) -o $(eval $cday) -eq $(eval $last_sa)) || test $(eval $nm1wd) -eq 2 -a $(eval $cday) -eq $(eval $last_sa) || t
est $(eval $cday) -eq 1 -a $(eval $cwday) -ge 2 -a $(eval $cwday) -le 6) && echo true'
apr_d='LANG=en date +%d%b%a'
apr_eom='$(test $(eval $apr_d) = "28AprSat" -o $(eval $apr_d) = "29AprSun" -o $(eval $apr_d) = "28AprMon" -o $(eval $apr_d) = "29AprTue") && echo true || echo false'
apr_no_eom='$(test $(eval $apr_d) = "30AprMon" -o $(eval $apr_d) = "01MayTue" -o $(eval $apr_d) = "30AprWed" -o $(eval $apr_d) = "01MayThu") && echo true || echo false'
END_OF_MONTH='$(test $(eval $apr_eom) = "true" -a $(eval $cy) != "2031" ) && echo true || echo $(eval $_END_OF_MONTH)'
END_OF_MONTH='$(test $(eval $apr_no_eom) = "true" -a $(eval $cy) != "2031" ) && echo false || echo $(eval $_END_OF_MONTH)'
END_OF_MONTH='$(test \( $(eval $apr_d) = "27AprSat" -o $(eval $apr_d) = "28AprSun" \) -a $(eval $cy) = "2041") && echo true || echo $(eval $_END_OF_MONTH)'
END_OF_MONTH='$(test \( $(eval $apr_d) = "30AprTue" -o $(eval $apr_d) = "01MayWed" \) -a $(eval $cy) = "2041") && echo false || echo $(eval $_END_OF_MONTH)'
 

00    02    *   *     *           [ "$(echo $(eval $END_OF_MONTH))" != "true" ] && su - app_user -c "/home/app_user/app stop"

00    03    *   *     1-7         [ "$(echo $(eval $END_OF_MONTH))" != "true" ] && su - app_user -c "/home/app_user/app restart"

The logic is build around using bash eval and test buildin commands. If END_OF_MONTH is set to true, then nothing happens, app stop and restart tasks aren't run. There are couple of exceptions regarding to my country : 1st of May and Cristian Orthodox holiday named Radonica are always day off; because of this the movements of day off and working days are possible. I tried to count these exceptions in crontab too.

Good Luck !


No comments:

Post a Comment