I've been tasked with setting up a schedule for a command to run on the 2nd Tuesday of each month, at 10am. I haven't been able to find any real way to test when the command will actually run.
This is what I have at the moment:
$schedule->command('foo')
->monthly()
->tuesdays()
->at('10:00')
->when(function () {
return Carbon::now()->weekOfMonth == 2;
})
->withoutOverlapping()
->appendOutputTo(storage_path($logPath));
Am I correct in thinking that the 'when' closure is evaluated only when the command is actually scheduled to run? Do I even need the 'monthly' condition? And most importantly: will this only run on the 2nd Tuesday of each month, at 10am?
Also, is there a way I could test this schedule without running the command (e.g. by passing in a date and getting a true/false if it'll run)?
Thanks
CodePudding user response:
The scheduler helper methods just build a cron expression, so we can look at the expression from your example to see if it does what you expect. Running:
(new Illuminate\Console\Scheduling\Schedule())->command('')->monthly()->tuesdays()->at('10:00')->expression
return "0 10 1 * 2"
which means run at 10am on the 1st of every month and and Tuesdays. (You can check that, and other expressions, here).
Your when
filter will ensure it does not get run on the 1st of the month, but there is no need for the monthly()
call as without it you get the expression "0 10 * * 2"
which means at 10am on Tuesdays.
As for testing if it works you can mock the current time for Carbon
. The following code will check if the command will run at the specified time:
$schedule = new \Illuminate\Console\Scheduling\Schedule();
$event = $schedule->command('foo')
->tuesdays()
->at('10:00')
->when(function () {
return Carbon::now()->weekOfMonth == 2;
})
->withoutOverlapping()
->appendOutputTo(storage_path($logPath));
Carbon::setTestNow('2023-01-10 10:00:00');
$event->isDue(app()) && $event->filtersPass(app()); // Returns true
Carbon::setTestNow('2023-01-17 10:00:00');
$event->isDue(app()) && $event->filtersPass(app()); // Returns false
If you wanted to run this in a unit test you can get the schedules defined in the kernel with resolve(Illuminate\Console\Scheduling\Schedule::class)->events();
you can then find the event in question and call the isDue
and filtersPass
method to see if it would run at the specified time. Laravel also offers some helper methods in the base TestCase
class for mocking out the time: https://laravel.com/docs/9.x/mocking#interacting-with-time
CodePudding user response:
From laravel documentation:
If you would like to view an overview of your scheduled tasks and the next time they are scheduled to run, you may use the schedule:list Artisan command
php artisan schedule:list
If I do it with the code you provided this is what I get
------------------------------------ ------------ ------------- ----------------------------
| Command | Interval | Description | Next Due |
------------------------------------ ------------ ------------- ----------------------------
| '/usr/local/bin/php' 'artisan' foo | 0 10 1 * 2 | | 2023-01-17 10:00:00 00:00 |
------------------------------------ ------------ ------------- ----------------------------
And if we take the listed interval
and put it into a tool like cronhub's crontab expression generator, it will tell you it will run At 10:00 AM, on day 1 of the month, and on Tuesday. This means that it will only run every 1st of the month, but only if it falls on Tuesday. Although in Next due
column it shows it will run next Tuesday 2023-01-17 10:00:00 00:00
I think this is an error in Laravel.
So, what you actually want to do is remove the ->monthly()
interval so that the command runs every Tuesday, then a callback function you provided with when()
is executed and if it returns true it will proceed, meaning your command will only run every second Tuesday in a month.
Or, you could acomplish the same thing by using the cron expressions directly, like this:
$schedule->command('foo')
->cron('0 10 * * TUE#2')
->withoutOverlapping();
->appendOutputTo(storage_path($logPath));
If I run php artisan schedule:list
today, it will correctly list the next due date to be February 14th 2023, which is second Tuesday is that month
------------------------------------ ---------------- ------------- ----------------------------
| Command | Interval | Description | Next Due |
------------------------------------ ---------------- ------------- ----------------------------
| '/usr/local/bin/php' 'artisan' foo | 0 10 * * TUE#2 | | 2023-02-14 10:00:00 00:00 |
------------------------------------ ---------------- ------------- ----------------------------