skip to content

System: Setting CRON in a different time zone

If you are in a different time zone to some of your clients, or have a server that uses UTC to avoid daylight saving changes, it can be tricky to set a CRON script running at the correct time in a different time zone. Here we explore a couple of solutions.

CRON before midnight

As a starting point, let's consider the requirement to run a CRON script at midnight to generate an accurate report on the days activities.

# m h dom mon dow command 0 0 * * * www-data /usr/bin/php $CRONSCRIPT

Here the script $CRONSCRIPT (and yes, we do have some CRON scripts using PHP) will be called every date at midnight server time.

A few seconds either way may not matter, but when working across time zones with different daylight saving regimes (especially between hemispheres) you might find your script running any time between 10PM and 2AM during the year.

Specifying a CRON time zone

The obvious solution is to specify a time zone for CRON:

# m h dom mon dow command TZ=Australia/Sydney 0 0 * * * www-data /usr/bin/php $CRONSCRIPT

Unfortunately, setting the time zone here has no effect on when the CRON script is triggered. What it does instead is set an environment variable inside the shell in which the script is called. The script itself may then use the TZ time zone.

We can see that by running the following test:

# m h dom mon dow command MAILTO=root 0 * * * * www-data /usr/bin/php -r 'echo date("Y-m-d H:i:s\n");' TZ=Australia/Sydney 0 * * * * www-data /usr/bin/php -r 'echo date("Y-m-d H:i:s\n");' TZ=America/New_York 0 * * * * www-data /usr/bin/php -r 'echo date("Y-m-d H:i:s\n");'

In this case both the PHP 'echo' commands are run on the hour. The first will output current server time, the second the current time in Sydney, and the third the time in New York.

An intelligent script

The above results present one simple solution. Rather than trying to trigger our CRON script in a different time zone, we can have it triggered every hour and then within the script itself check whether it is midnight in the specified time zone (TZ).

<?PHP if(date('Hi') == "0000") { ... it's midnight - run the report ... } ?>

While this works, it's not the most efficient approach as it requires executing the PHP script (with all associated overheads) every hour rather than just once each day (or week, or month, depending on your requirements). It also makes testing difficult.

Making CRON conditional

A better approach is to have CRON decide whether it's time to run the PHP script. While we've ruled out the possibility of doing this with CRON settings, there are more ways to skin the cat.

Using the command line we can string commands together using a logical operator, and make calling the PHP script conditional on a command-line date calculation.

For example, the following bash command will result in the PHP command being executed only if it is run exactly at midnight:

$ [ "$(date +%H%M)" == "0000" ] && php -r 'echo date("Y-m-d H:i:s\n");'

So let's try plugging this into our CRON file:

# m h dom mon dow command TZ=Australia/Sydney 0 * * * * www-data [ "$(date +%H%M)" == "0000" ] && /usr/bin/php -r 'echo date("Y-m-d H:i:s\n");'

This is where we have a problem. Instead of working as intended, what we get is an error:

/bin/sh: 1: Syntax error: end of file unexpected (expecting ")")

Thankfully, there is a clue there. CRON runs under /bin/sh which these days tends to resolve to /bin/dash. The simplest solution, short of re-writing the conditional in dash, is to force kindly ask CRON to use bash instead:

# m h dom mon dow command SHELL=/bin/bash TZ=Australia/Sydney 0 * * * * www-data [ "$(date +%H%M)" == "0000" ] && /usr/bin/php -r 'echo date("Y-m-d H:i:s\n");'

But we still get errors:

/bin/bash: -c: line 0: unexpected EOF while looking for matching `)' /bin/bash: -c: line 1: syntax error: unexpected end of file

After some searching it appears the problem is that we need to escape our % characters:

# m h dom mon dow command SHELL=/bin/bash TZ=Australia/Sydney 0 * * * * www-data [ "$(date +\%H\%M)" == "0000" ] && /usr/bin/php $CRONSCRIPT

Success! We now have a means of triggering a script at midnight in any time zone. The CRON command will be triggered every hour, but only continue to running the PHP script if it's midnight in the target time zone.

End of month CRON in another time zone

This is a slightly more complicated requirement, for a script to run only at midnight on the 1st of the month. Using the previous script as a starting point, we come up with the following:

# m h dom mon dow command SHELL=/bin/bash TZ=Australia/Sydney 0 * 28-31 * * www-data [ "$(date +\%d\%H)" == "0100" ] && /usr/bin/php -r 'echo date("Y-m-d H:i:s\n");'

Note that we have made the assumption that the time in Sydney will always be ahead of server time. So we trigger the CRON as we approach the end of each month (dates 28-31) and use bash to check whether Sydney has moved into the next month.

For a more generic solution you need to use 1,28-31 to cover the situation where the server time is ahead of the target time zone (TZ).

< System

User Comments

Post your comment or question

15 March, 2024

There is also the CRON_TZ variable.

# m h dom mon dow command

MAILTO=root

0 * * * * www-data /usr/bin/php -r 'echo date("Y-m-d H:i:s\n");'

CRON_TZ=Australia/Sydney
0 * * * * www-data /usr/bin/php -r 'echo date("Y-m-d H:i:s\n");'

CRON_TZ=America/New_York
0 * * * * www-data /usr/bin/php -r 'echo date("Y-m-d H:i:s\n");'

The CRON_TZ variable specifies the time zone for the cron table. This allows the start time to be specified for midnight in New York and Sydney as well as the local time zone.

The CRON_TZ option does not appear to be available on Debian, but may work for other systems.

Good to know, and thank you Lee

top