Run a scheduler command on workdays excluding holidays

Jul 7, 2022

Laravel has some very helpful methods that allow us to customize when our commands are executed. Unfortunately, sometimes we need a little more control that Laravel doesn't offer right out of the box.

For example, when you need to execute a command every workday, that means, every weekday but excluding holidays. You wouldn't want your coworkers (or yourself) to receive that daily email on Christmas morning.

In this article, I'll show you how to do precisely that.

Avoid weekends

By default, Laravel comes with the helper method weekdays to configure the schedule command to only be executed on weekdays. Let's say that we have a command with the signature send:daily-email, and we want this command to only be sent on weekdays (Monday to Friday), we can do this very easily by adding this to our schedule method in our Console Kernel:

// app/Console/Kernel.php

class Kernel extends ConsoleKernel
{
    //...
    
    protected function schedule(Schedule $schedule)
    {
        $schedule->command('send:daily-email')->weekdays();
    }
    
    //...
}

And that is it, we just need to call the weekdays method on our command and Laravel will automatically execute it only on weekdays.

Now that we have the weekdays down. We need to figure out how to make sure Laravel skips holidays.

Skipping holidays

To do this, we'll be using the skip method in the schedule command. This method executes the function passed as a parameter and if that function returns true, it skips that execution. That way we can check if the current day is a holiday or not.

protected function schedule(Schedule $schedule)
{
    $schedule->command('send:daily-email')->weekdays()->skip(function () {
        return false; // TODO: check if today is a holiday
    });
}

Now we need to figure out if today is a holiday. There are many ways to do this, but there is a library that can help us with this by adding a mixin to Carbon. You can read more about it here: https://github.com/kylekatarnls/business-day

Install the library using composer:

composer require cmixin/business-day

Once we install it, we need to enable the mixin in a service provider. The AppServiceProvider is a good place to do it.

// app/Providers/AppServiceProvider.php


use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Carbon;
use Cmixin\BusinessDay;

class AppServiceProvider extends ServiceProvider
{
    // ... 
    
    public function boot()
    {
        BusinessDay::enable(Carbon::class);
    }
}

In the boot method of the service provider, we call the enable method of the BusinessDay class, which is part of the library that we just installed. We also need to pass the carbon class to which we will be adding the extra methods. This class is Illuminate\Support\Carbon.

Now we need to tell the library which region's holidays we are using. You can find all the supported regions here: https://github.com/kylekatarnls/business-day/tree/master/src/Cmixin/Holidays

For example, I'm going to use the US national holidays. That means that the file I'm looking for is us-national.php, we are going to use that filename to configure our holidays.

public function boot()
{
    BusinessDay::enable(Carbon::class);
    Carbon::setHolidaysRegion('us-national');
}

And that is it, now we can know if a day is a holiday by calling the isHoliday method on a carbon instance, for example:

now()->isHoliday();

Putting it all together

We can now finish our command configuration by adding the holiday check to our skip method:

protected function schedule(Schedule $schedule)
{
    $schedule->command('send:daily-email')->weekdays()->skip(function () {
        return now()->isHoliday();
    });
}

And we are done, now every weekday Laravel will check if it's a holiday, and if it isn't the command will be executed.