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