Experiments with WP Cron

I’ve got a couple of projects coming up which are going to require scheduling tasks in WordPress either to go off and get stuff, or to check whether posts displayed are still accurate, etc. Luckily, WordPress has a pseudo-Cron implementation which I plan to use to .

Let’s take a step back first and remember what Cron is: Cron is the scheduling service for UNIX-like systems which can set tasks occur at specific times or to recur at specific intervals. So you can use Cron to tell your system to do something at 12:47 on the 14th April, 2009 or to perform a task at 12 minutes past midnight every Monday, it’s a very useful service.

WordPress is not naturally a proactive beast, so unfortunately your WP site will not spend it’s day checking the time and seeing if it’s got anything to do. This is where the pseudo come in, with the WordPress pseudo-Cron we’re relying on a constant flow of visitors to trigger WordPress into checking for scheduled tasks. The upshot of this is that we cannot rely on WordPress to precisely schedule events, but the system is good enough for most things. (We can always increase the frequency of visitors artificially by targetting the site with five minute checks by Pingdom or similar, with the added benefit that the site is now monitored for downtime.)

So let’s get into the details of how all this stuff works, shall we?To understand WP Cron a little better, I wrote a small test plugin called “Test Cron“. This plugin writes the existing Cron schedules to the error log, schedules some one off tasks when the plugin is registered, writes the schedules to the error log, and when the tasks are executed by WP Cron it writes more information to the error log.

It turns out that WP Cron is fairly straightforward, with a few gotchas (well they got me at least). The basic structure of scheduling a one off event is:

  1. Add a one off or scheduled event with a timestamp, a hook name, and some arguments to be passed to the hook
  2. Add a function to execute when the hook is called
  3. Add an action, tied to the hook name

The fact that a Cron event calls a hook, rather than (as I initially thought) a function, is pretty useful as it means you can have multiple actions triggered by the same Cron event.

To set a one off event, you use the function wp_schedule_single_event and to schedule recurring events you use wp_schedule_event. WordPress supports recurrences of hourly, daily and twice daily by default, but it’s possible to extend the variety of schedules using the cron_schedules filter.

The other thing to bear in mind is the headache of timezones. WP Cron expects that the timestamps passed to wp_schedule_event and wp_schedule_single_event are in the server time, and (looking at the code for the WordPress function get_gmt_from_date) WP expects the server time to be GMT. If you’re asking users to enter times/dates to schedule an event you will need to convert the user’s date time into a timestamp, then do something like the following to adjust to GMT:

$timestamp += get_option('gmt_offset') * 3600);

Bear in mind that your user will need to know the timezone they should be entering otherwise you will end up with wildly inaccurate timestamps in your Cron, make sure you signpost the expected timezone clearly on the UI.

WP Cron is triggered by a network request, this means that if your site is only accessible from certain IPs or protected by HTTP Basic Authentication then you may have problems. Add an exception to your .htaccess file or in your Apache config to get around this (see my previous post on Basic Authentication and WordPress for more).

I needed to clear all the events which were going to call my hook at one point, and to do this I used the following code. Don’t assume, as I initially did, that because the $args array was passing into wp_next_scheduled on an optional argument, if I didn’t pass it then the function would return all events on that hook regardless of arguments; this is not so, you need to match any arguments as well as the hook name to find the events here. (If your events have no arguments, then you need only pass in the hook name.)

while ( $next = wp_next_scheduled( $hook, $args ) ) {
wp_unschedule_event( $next, 'lifespan_expire_post', $this->args );
break;
}

The weirdest gotcha in all of this has to be the Unix apocalypse. You’ll have heard of the Unix epoch? The epoch was on Thu, 01 Jan 1970 at 00:00:00 GMT. Unix time also has an end, and that end is on Tues, 19 Jan 2038 at 03:14:07 GMT. I hit this when I picked a fanciful date in 2044 for testing, unfortunately PHP’s mktime doesn’t seem to throw any notices, it just quietly returns false which my code then interpreted to 0… rather unhelpfully.

If you are developing with WordPress Cron then you might find my Cron View plugin useful, it presents all the current Cron events and schedules in an admin page. I found it handy to use to check that jobs had been scheduled as I expected.

So… I hope at least one of those random Cron related ramblings was of use to someone else. Good luck scheduling your WordPress tasks.

13 thoughts on “Experiments with WP Cron

  1. simonwheatley Post author

    Why do I not see these things until after I’ve spent time writing similar works? I’ve just come across WP Crontrol, which looks great for checking the Cron events, and also adding new events.

    Reply
  2. Dennis

    Trying to make the cron job performed every minute, but it won’t work. Could some, please, tell me what is wrong with the following code? And why isn’t cron_schedules filter being saved?

    add_filter(‘cron_schedules’, ‘sp_more_cron_reccurences’);
    wp_schedule_event(time(), ‘minutely’, ‘sp_add_post_event’);

    function sp_more_cron_reccurences() {
    return array(
    ‘minutely’ => array(‘interval’ => 60, ‘display’ => ‘Once A Minute’),
    ‘quarterly’ => array(‘interval’ => 900, ‘display’ => ‘Once Every 15 Minutes’),
    );
    }

    Reply
  3. Dennis

    Got it! WP Cron is actually a pseudo cron, which relies on visitors traffic, so if you don’t have enough traffic cron schedule will not be performed in time. I wonder if it’s possible to set real Cronjob on Unix/Linux server to execute WP Pseudo Cron in WordPress on hourly basis, so that performance doesn’t depend on visitors?

    Reply
    1. simonwheatley Post author

      You could setup something on a cron job that pinged your site’s homepage every however many minutes; you could also use one of the free site uptime monitors and then you also get uptime monitoring!

      Reply
  4. Dennis

    Oh, I forgot to include that line. But that line is present, like the following:

    add_action(“sp_add_post_event”, “sp_add_post”);

    I found out that there is a problem with wp-cron. wp_reschedule_event() reschedules event on a proper date in future, but after I reload any page of blog an event is being REMOVED from cron array, and I have no idea why??

    Reply
  5. Brent Shepherd

    Very handy tutorial, thanks! The codex is a little lite on the general useage of wp-cron.

    Just one mixup though, the functions are in reverse order in this sentence:
    “To set a one off event, you use the function wp_schedule_event and to schedule recurring events you use wp_schedule_single_event.”

    Thanks again. :)

    Reply
  6. Brent Shepherd

    Hi Simon,

    Yep functions look right now. :)

    Experimenting with wp cron myself, it looks like the function hooked to the schedule item doesn’t receive the full ‘arg’ array. It receives the first value in the array, but not the rest and no array keys (if an associative array).

    Did you find this also?

    I also tested it with your plugin’s code to make sure and it was reproducible.

    Feel free to ignore this, I’ll probably email the wp-hackers list about it too. :)

    Reply
  7. Pingback: WordPress: WP-Cron and Basic Authentication

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>