The default cron job daemon on Debian and Ubuntu does not support per-user timezones (see crontab(5) man page).
Here is a solution which runs hourly cron tasks in the timezone which you specified. For example, if you want to run “test.sh” at 5 AM and 7 PM in timezone Europe/Sofia, you need to create the following “cron.hourly” script:
#!/bin/bash /path/to/run-at.sh 5,19 Europe/Sofia $(( 15*60 )) /var/run/run-at-test.state /path/to/test.sh
Here is the source code of “run-at.sh”:
#!/bin/bash
set -u
# XXX
# We work exclusively with global variables.
# Functions are used just to separate logic and for self-documenting.
function display_usage() {
if [ "$1" -ge 5 ]; then
return; # enough parameters
fi
cat >&2 <<EOF
Usage: $0 HOURS TZ WARN_TIME STATE_FILE COMMAND [ARGS...]
Runs COMMAND every day at the specified HOURS.
The execution is accounted and considered successful
only if COMMAND exits with 0. If there was no (successful)
execution within the WARN_TIME hours specified period,
a warning is being issued.
The STATE_FILE must pre-exist, so make sure that you
create it before the first run, or you will get a
warning.
This script is intended to be run in "cron.hourly".
Arguments:
- HOURS -- comma-separated list; example: 5,12,19
- TZ -- time zone; example: Europe/Sofia
- WARN_TIME -- minutes; example: 360
- STATE_FILE -- fill path to a writable file
- COMMAND and ARGS to be executed on HOURS
Example:
$0 5,19 Europe/Sofia \\
\$(( 15*60 )) /root/.run-at-test.state \\
date -d now +%c
EOF
exit 1
}
function check_state_file_oldness() {
# find file with mtime less than $WARN_TIME minutes
if [ "$(find "$STATE_FILE" -type f -mmin -"$WARN_TIME")" != "$STATE_FILE" ]; then
# file not found -> this will be indicated by 'find' on STDERR too
# file is too old ("-mmin" condition not met)
echo "WARNING: No successful run in the last $WARN_TIME minutes" >&2
fi
}
function split_hours() {
HOURS="${HOURS//,/ }" # replace "," with " "
# https://blog.famzah.net/2013/02/17/
# bash-split-a-string-into-columns-by-white-space-without-invoking-sub-shells/
HOURS=( $HOURS ) # now an ARRAY
}
function validate_tz() {
# naive check; tested on Debian
if [ ! -e "/usr/share/zoneinfo/$WANT_TZ" ]; then
echo "ERROR: TZ seems to be invalid." >&2
exit 1
fi
}
function get_now_hour_in_tz() {
NOW_HOUR="$(TZ="$WANT_TZ" date +%H)" # get current hour in 24h-format using the $WANT_TZ
}
function check_if_we_should_run_or_exit() {
RUN=0
for h in "${HOURS[@]}" ; do
if [ "$h" -eq "$NOW_HOUR" ]; then
RUN=1
break
fi
done
if [ "$RUN" -eq 0 ]; then
exit 0
fi
}
function execute_command_and_get_exit_code() {
"$@" # execute the command
EC="$?"
}
function update_state_file_mtime() {
if [ "$EC" -eq 0 ]; then
touch "$STATE_FILE"
fi
}
#### ### ### ###
display_usage "$#"
# parse_argv
HOURS="$1" ; shift
WANT_TZ="$1" ; shift
WARN_TIME="$1" ; shift
STATE_FILE="$1" ; shift
# the rest in "$@" is the command to be executed
check_state_file_oldness
split_hours
validate_tz
get_now_hour_in_tz
check_if_we_should_run_or_exit
execute_command_and_get_exit_code "$@"
update_state_file_mtime